From 7656cc07accdb2811cbe73108253d88de03da911 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 8 Oct 2024 19:15:49 +0200 Subject: [PATCH 1/5] ensure `mergeRefs` is stable in `ListboxOptions` --- packages/@headlessui-react/src/components/listbox/listbox.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 7fc4e4a01..395ff69c1 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -934,6 +934,7 @@ function OptionsFn( transition = false, ...theirProps } = props + let mergeRefs = useMergeRefsFn() let anchor = useResolvedAnchor(rawAnchor) // To improve the correctness of transitions (timing related race conditions), @@ -1165,6 +1166,7 @@ function OptionsFn( value={data.mode === ValueMode.Multi ? data : { ...data, isSelected }} > {render({ + mergeRefs, ourProps, theirProps, slot, From 0fb121651d11d8b5be90c6565687003e7eac07cb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 8 Oct 2024 19:15:52 +0200 Subject: [PATCH 2/5] introduce internal `useRender` hook We already use the `render` function helper in all of our components, but we also need _some_ way of storing persistent functions (such as the `mergeRefsFn`). With this hook, we can embed it in the hook and we can ensure it's always stable. We also won't run into an issue where we accidentally forget something because it will be shipped by default. --- .../@headlessui-react/src/utils/render.test.tsx | 10 +++++++++- packages/@headlessui-react/src/utils/render.ts | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/@headlessui-react/src/utils/render.test.tsx b/packages/@headlessui-react/src/utils/render.test.tsx index 1d785b0dd..b6fc15268 100644 --- a/packages/@headlessui-react/src/utils/render.test.tsx +++ b/packages/@headlessui-react/src/utils/render.test.tsx @@ -2,7 +2,7 @@ import { getByTestId, prettyDOM, render as testRender } from '@testing-library/r import React, { Fragment, createRef, type ElementType, type Ref } from 'react' import { suppressConsoleLogs } from '../test-utils/suppress-console-logs' import type { Expand, Props } from '../types' -import { RenderFeatures, render, type PropsForFeatures } from './render' +import { RenderFeatures, useRender, type PropsForFeatures } from './render' function contents(id = 'wrapper') { return prettyDOM(getByTestId(document.body, id), undefined, { @@ -15,6 +15,7 @@ describe('Default functionality', () => { function Dummy( props: Props & Partial<{ a: any; b: any; c: any }> ) { + let render = useRender() return (
{render({ @@ -31,6 +32,7 @@ describe('Default functionality', () => { function DummyWithClassName( props: Props & Partial<{ className: string | (() => string) }> ) { + let render = useRender() return (
{render({ @@ -97,6 +99,7 @@ describe('Default functionality', () => { } function OtherDummy(props: Props) { + let render = useRender() return (
{render({ @@ -157,6 +160,7 @@ describe('Default functionality', () => { function Dummy( props: Props & Partial<{ a: any; b: any; c: any }> ) { + let render = useRender() return (
{render({ @@ -179,6 +183,7 @@ describe('Default functionality', () => { function Dummy( props: Props & Partial<{ a: any; b: any; c: any }> ) { + let render = useRender() return (
{render({ @@ -305,6 +310,7 @@ describe('Features.Static', () => { props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props + let render = useRender() return (
{render({ @@ -380,6 +386,7 @@ describe('Features.RenderStrategy', () => { props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props + let render = useRender() return (
{render({ @@ -408,6 +415,7 @@ describe('Features.Static | Features.RenderStrategy', () => { props: Expand & PropsForFeatures> & { show: boolean } ) { let { show, ...rest } = props + let render = useRender() return (
{render({ diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index 4106c4b68..3e9b90dd0 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -57,7 +57,16 @@ export type PropsForFeatures = Expand< > > -export function render({ +export function useRender() { + let mergeRefs = useMergeRefsFn() + + return useCallback( + (args: Parameters[0]) => render({ mergeRefs, ...args }), + [render, mergeRefs] + ) as typeof render +} + +function render({ ourProps, theirProps, slot, @@ -77,7 +86,7 @@ export function render -}) { +}): ReturnType | null { mergeRefs = mergeRefs ?? defaultMergeRefs let props = mergePropsAdvanced(theirProps, ourProps) @@ -281,7 +290,7 @@ function _render( * the `function` that updates these refs and can only do * so once the ref that contains the list is updated. */ -export function useMergeRefsFn() { +function useMergeRefsFn() { type MaybeRef = MutableRefObject | ((value: T) => void) | null | undefined let currentRefs = useRef[]>([]) let mergedRef = useCallback((value: any) => { From 6766f75cfb7d196c00bb14b9f71013743277f262 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 8 Oct 2024 19:15:58 +0200 Subject: [PATCH 3/5] use new `useRender()` hook --- .../src/components/button/button.tsx | 7 +++---- .../src/components/checkbox/checkbox.tsx | 4 +++- .../src/components/combobox/combobox.tsx | 15 +++++++++++---- .../data-interactive/data-interactive.tsx | 4 +++- .../components/description/description.tsx | 4 +++- .../src/components/dialog/dialog.tsx | 10 +++++++++- .../src/components/disclosure/disclosure.tsx | 13 +++++++------ .../src/components/field/field.tsx | 4 +++- .../src/components/fieldset/fieldset.tsx | 4 +++- .../src/components/focus-trap/focus-trap.tsx | 4 +++- .../src/components/input/input.tsx | 4 +++- .../src/components/label/label.tsx | 4 +++- .../src/components/listbox/listbox.tsx | 17 +++++++++++------ .../src/components/menu/menu.tsx | 19 +++++++++++++++---- .../src/components/popover/popover.tsx | 15 +++++++++++---- .../src/components/portal/portal.tsx | 8 +++++++- .../components/radio-group/radio-group.tsx | 8 +++++++- .../src/components/select/select.tsx | 4 +++- .../src/components/switch/switch.tsx | 6 +++++- .../src/components/tabs/tabs.tsx | 12 +++++++++++- .../src/components/textarea/textarea.tsx | 4 +++- .../src/components/tooltip/tooltip.tsx | 8 +++++++- .../src/components/transition/transition.tsx | 6 +++++- .../@headlessui-react/src/internal/hidden.tsx | 4 +++- 24 files changed, 142 insertions(+), 46 deletions(-) diff --git a/packages/@headlessui-react/src/components/button/button.tsx b/packages/@headlessui-react/src/components/button/button.tsx index 174b6a503..2ead53dff 100644 --- a/packages/@headlessui-react/src/components/button/button.tsx +++ b/packages/@headlessui-react/src/components/button/button.tsx @@ -9,8 +9,7 @@ import type { Props } from '../../types' import { forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -42,7 +41,6 @@ function ButtonFn( ref: Ref ) { let providedDisabled = useDisabled() - let mergeRefs = useMergeRefsFn() let { disabled = providedDisabled || false, autoFocus = false, ...theirProps } = props let { isFocusVisible: focus, focusProps } = useFocusRing({ autoFocus }) @@ -65,8 +63,9 @@ function ButtonFn( return { disabled, hover, focus, active, autofocus: autoFocus } satisfies ButtonRenderPropArg }, [disabled, hover, focus, active, autoFocus]) + let render = useRender() + return render({ - mergeRefs, ourProps, theirProps, slot, diff --git a/packages/@headlessui-react/src/components/checkbox/checkbox.tsx b/packages/@headlessui-react/src/components/checkbox/checkbox.tsx index f68ed348f..c964ff584 100644 --- a/packages/@headlessui-react/src/components/checkbox/checkbox.tsx +++ b/packages/@headlessui-react/src/components/checkbox/checkbox.tsx @@ -26,7 +26,7 @@ import { attemptSubmit } from '../../utils/form' import { forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -176,6 +176,8 @@ function CheckboxFn {name != null && ( diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index b6ab9e171..8c395f944 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -69,8 +69,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -949,6 +948,8 @@ function ComboboxFn( let data = useData('Combobox.Button') let actions = useActions('Combobox.Button') let buttonRef = useSyncRefs(ref, actions.setButtonElement) - let mergeRefs = useMergeRefsFn() let internalId = useId() let { @@ -1610,8 +1612,9 @@ function ButtonFn( pressProps ) + let render = useRender() + return render({ - mergeRefs, ourProps, theirProps, slot, @@ -1813,6 +1816,8 @@ function OptionsFn( }) } + let render = useRender() + return ( ({ ...context.slot, disabled }), [context.slot, disabled]) let ourProps = { ref: descriptionRef, ...context.props, id } + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/dialog/dialog.tsx b/packages/@headlessui-react/src/components/dialog/dialog.tsx index f4e965b83..590660ee5 100644 --- a/packages/@headlessui-react/src/components/dialog/dialog.tsx +++ b/packages/@headlessui-react/src/components/dialog/dialog.tsx @@ -41,7 +41,7 @@ import { match } from '../../utils/match' import { RenderFeatures, forwardRefWithAs, - render, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -286,6 +286,8 @@ let InternalDialog = forwardRefWithAs(function InternalDialog< } } + let render = useRender() + return ( @@ -450,6 +452,8 @@ function PanelFn( let Wrapper = transition ? TransitionChild : Fragment let wrapperProps = transition ? { unmount } : {} + let render = useRender() + return ( {render({ @@ -494,6 +498,8 @@ function BackdropFn( let Wrapper = transition ? TransitionChild : Fragment let wrapperProps = transition ? { unmount } : {} + let render = useRender() + return ( {render({ @@ -541,6 +547,8 @@ function TitleFn( let ourProps = { ref: titleRef, id } + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx index 1ae968464..03a6e92e0 100644 --- a/packages/@headlessui-react/src/components/disclosure/disclosure.tsx +++ b/packages/@headlessui-react/src/components/disclosure/disclosure.tsx @@ -41,8 +41,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -233,6 +232,8 @@ function DisclosureFn( ref: disclosureRef, } + let render = useRender() + return ( @@ -304,7 +305,6 @@ function ButtonFn( return dispatch({ type: ActionTypes.SetButtonElement, element }) }) ) - let mergeRefs = useMergeRefsFn() useEffect(() => { if (isWithinPanel) return @@ -411,8 +411,9 @@ function ButtonFn( pressProps ) + let render = useRender() + return render({ - mergeRefs, ourProps, theirProps, slot, @@ -451,7 +452,6 @@ function PanelFn( } = props let [state, dispatch] = useDisclosureContext('Disclosure.Panel') let { close } = useDisclosureAPIContext('Disclosure.Panel') - let mergeRefs = useMergeRefsFn() // To improve the correctness of transitions (timing related race conditions), // we track the element locally to this component, instead of relying on the @@ -496,11 +496,12 @@ function PanelFn( ...transitionDataAttributes(transitionData), } + let render = useRender() + return ( {render({ - mergeRefs, ourProps, theirProps, slot, diff --git a/packages/@headlessui-react/src/components/field/field.tsx b/packages/@headlessui-react/src/components/field/field.tsx index 27d9ae154..c521341fb 100644 --- a/packages/@headlessui-react/src/components/field/field.tsx +++ b/packages/@headlessui-react/src/components/field/field.tsx @@ -6,7 +6,7 @@ import { DisabledProvider, useDisabled } from '../../internal/disabled' import { FormFieldsProvider } from '../../internal/form-fields' import { IdProvider } from '../../internal/id' import type { Props } from '../../types' -import { forwardRefWithAs, render, type HasDisplayName } from '../../utils/render' +import { forwardRefWithAs, useRender, type HasDisplayName } from '../../utils/render' import { useDescriptions } from '../description/description' import { useLabels } from '../label/label' @@ -44,6 +44,8 @@ function FieldFn( 'aria-disabled': disabled || undefined, } + let render = useRender() + return ( diff --git a/packages/@headlessui-react/src/components/fieldset/fieldset.tsx b/packages/@headlessui-react/src/components/fieldset/fieldset.tsx index ae8a1eab2..c30361cdb 100644 --- a/packages/@headlessui-react/src/components/fieldset/fieldset.tsx +++ b/packages/@headlessui-react/src/components/fieldset/fieldset.tsx @@ -5,7 +5,7 @@ import { useResolvedTag } from '../../hooks/use-resolved-tag' import { useSyncRefs } from '../../hooks/use-sync-refs' import { DisabledProvider, useDisabled } from '../../internal/disabled' import type { Props } from '../../types' -import { forwardRefWithAs, render, type HasDisplayName } from '../../utils/render' +import { forwardRefWithAs, useRender, type HasDisplayName } from '../../utils/render' import { useLabels } from '../label/label' let DEFAULT_FIELDSET_TAG = 'fieldset' as const @@ -50,6 +50,8 @@ function FieldsetFn( 'aria-disabled': disabled || undefined, } + let render = useRender() + return ( diff --git a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx index 6ee1a539d..482f2dd33 100644 --- a/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx +++ b/packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx @@ -24,7 +24,7 @@ import { history } from '../../utils/active-element-history' import { Focus, FocusResult, focusElement, focusIn } from '../../utils/focus-management' import { match } from '../../utils/match' import { microTask } from '../../utils/micro-task' -import { forwardRefWithAs, render, type HasDisplayName, type RefProp } from '../../utils/render' +import { forwardRefWithAs, useRender, type HasDisplayName, type RefProp } from '../../utils/render' type Containers = // Lazy resolved containers @@ -197,6 +197,8 @@ function FocusTrapFn( }, } + let render = useRender() + return ( <> {tabLockEnabled && ( diff --git a/packages/@headlessui-react/src/components/input/input.tsx b/packages/@headlessui-react/src/components/input/input.tsx index 212465ee3..e4665dc22 100644 --- a/packages/@headlessui-react/src/components/input/input.tsx +++ b/packages/@headlessui-react/src/components/input/input.tsx @@ -10,7 +10,7 @@ import type { Props } from '../../types' import { forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -78,6 +78,8 @@ function InputFn( return { disabled, invalid, hover, focus, autofocus: autoFocus } satisfies InputRenderPropArg }, [disabled, invalid, hover, focus, autoFocus]) + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/label/label.tsx b/packages/@headlessui-react/src/components/label/label.tsx index 73c7c96b3..edde21637 100644 --- a/packages/@headlessui-react/src/components/label/label.tsx +++ b/packages/@headlessui-react/src/components/label/label.tsx @@ -17,7 +17,7 @@ import { useSyncRefs } from '../../hooks/use-sync-refs' import { useDisabled } from '../../internal/disabled' import { useProvidedId } from '../../internal/id' import type { Props } from '../../types' -import { forwardRefWithAs, render, type HasDisplayName, type RefProp } from '../../utils/render' +import { forwardRefWithAs, useRender, type HasDisplayName, type RefProp } from '../../utils/render' // --- @@ -203,6 +203,8 @@ function LabelFn( } } + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/listbox/listbox.tsx b/packages/@headlessui-react/src/components/listbox/listbox.tsx index 395ff69c1..fdde28dd4 100644 --- a/packages/@headlessui-react/src/components/listbox/listbox.tsx +++ b/packages/@headlessui-react/src/components/listbox/listbox.tsx @@ -74,8 +74,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -698,6 +697,8 @@ function ListboxFn< return theirOnChange?.(defaultValue) }, [theirOnChange, defaultValue]) + let render = useRender() + return ( ( autoFocus = false, ...theirProps } = props - let mergeRefs = useMergeRefsFn() let buttonRef = useSyncRefs(ref, useFloatingReference(), actions.setButtonElement) let getFloatingReferenceProps = useFloatingReferenceProps() @@ -881,8 +881,9 @@ function ButtonFn( pressProps ) + let render = useRender() + return render({ - mergeRefs, ourProps, theirProps, slot, @@ -934,7 +935,6 @@ function OptionsFn( transition = false, ...theirProps } = props - let mergeRefs = useMergeRefsFn() let anchor = useResolvedAnchor(rawAnchor) // To improve the correctness of transitions (timing related race conditions), @@ -1160,13 +1160,14 @@ function OptionsFn( ...transitionDataAttributes(transitionData), }) + let render = useRender() + return ( {render({ - mergeRefs, ourProps, theirProps, slot, @@ -1338,6 +1339,8 @@ function OptionFn< } : {} + let render = useRender() + if (!selected && usedInSelectedOption) { return null } @@ -1385,6 +1388,8 @@ function SelectedFn {render({ diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index 251cc021a..f93afd2ed 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -67,8 +67,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -426,6 +425,8 @@ function MenuFn( let ourProps = { ref: menuRef } + let render = useRender() + return ( @@ -484,7 +485,6 @@ function ButtonFn( } = props let [state, dispatch] = useMenuContext('Menu.Button') let getFloatingReferenceProps = useFloatingReferenceProps() - let mergeRefs = useMergeRefsFn() let buttonRef = useSyncRefs( ref, useFloatingReference(), @@ -571,8 +571,9 @@ function ButtonFn( pressProps ) + let render = useRender() + return render({ - mergeRefs, ourProps, theirProps, slot, @@ -820,6 +821,8 @@ function ItemsFn( ...transitionDataAttributes(transitionData), }) + let render = useRender() + return ( {render({ @@ -982,6 +985,8 @@ function ItemFn( onMouseLeave: handleLeave, } + let render = useRender() + return ( @@ -1018,6 +1023,8 @@ function SectionFn( let theirProps = props let ourProps = { ref, 'aria-labelledby': labelledby, role: 'group' } + let render = useRender() + return ( {render({ @@ -1055,6 +1062,8 @@ function HeadingFn( let ourProps = { id, ref, role: 'presentation', ...context.props } + let render = useRender() + return render({ ourProps, theirProps, @@ -1083,6 +1092,8 @@ function SeparatorFn( let theirProps = props let ourProps = { ref, role: 'separator' } + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/popover/popover.tsx b/packages/@headlessui-react/src/components/popover/popover.tsx index 386ec3579..3d772c2e0 100644 --- a/packages/@headlessui-react/src/components/popover/popover.tsx +++ b/packages/@headlessui-react/src/components/popover/popover.tsx @@ -74,8 +74,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, - useMergeRefsFn, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -419,6 +418,8 @@ function PopoverFn( let ourProps = { ref: popoverRef } + let render = useRender() + return ( @@ -707,6 +708,8 @@ function ButtonFn( } }) + let render = useRender() + return ( <> {render({ @@ -799,6 +802,8 @@ function BackdropFn( ...transitionDataAttributes(transitionData), } + let render = useRender() + return render({ ourProps, theirProps, @@ -884,7 +889,6 @@ function PanelFn( setLocalPanelElement ) let ownerDocument = useOwnerDocument(internalPanelRef) - let mergeRefs = useMergeRefsFn() useIsoMorphicEffect(() => { dispatch({ type: ActionTypes.SetPanelId, panelId: id }) @@ -1070,6 +1074,8 @@ function PanelFn( } }) + let render = useRender() + return ( @@ -1087,7 +1093,6 @@ function PanelFn( /> )} {render({ - mergeRefs, ourProps, theirProps, slot, @@ -1188,6 +1193,8 @@ function GroupFn( let theirProps = props let ourProps = { ref: groupRef } + let render = useRender() + return ( diff --git a/packages/@headlessui-react/src/components/portal/portal.tsx b/packages/@headlessui-react/src/components/portal/portal.tsx index efc0cba2f..4c05ce070 100644 --- a/packages/@headlessui-react/src/components/portal/portal.tsx +++ b/packages/@headlessui-react/src/components/portal/portal.tsx @@ -23,7 +23,7 @@ import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs' import { usePortalRoot } from '../../internal/portal-force-root' import type { Props } from '../../types' import { env } from '../../utils/env' -import { forwardRefWithAs, render, type HasDisplayName, type RefProp } from '../../utils/render' +import { forwardRefWithAs, useRender, type HasDisplayName, type RefProp } from '../../utils/render' function usePortalTarget(ref: MutableRefObject): HTMLElement | null { let forceInRoot = usePortalRoot() @@ -129,6 +129,7 @@ let InternalPortalFn = forwardRefWithAs(function InternalPortalFn< } }) + let render = useRender() if (!ready) return null let ourProps = { ref: portalRef } @@ -154,6 +155,9 @@ function PortalFn( let portalRef = useSyncRefs(ref) let { enabled = true, ...theirProps } = props + + let render = useRender() + return enabled ? ( ) : ( @@ -193,6 +197,8 @@ function GroupFn( let ourProps = { ref: groupRef } + let render = useRender() + return ( {render({ diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx index bfeaee803..a43d33425 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -35,7 +35,7 @@ import { getOwnerDocument } from '../../utils/owner' import { forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -309,6 +309,8 @@ function RadioGroupFn @@ -444,6 +446,8 @@ function OptionFn< } satisfies OptionRenderPropArg }, [checked, disabled, hover, focus, autoFocus]) + let render = useRender() + return ( @@ -557,6 +561,8 @@ function RadioFn< return { checked, disabled, hover, focus, autofocus: autoFocus } satisfies RadioRenderPropArg }, [checked, disabled, hover, focus, autoFocus]) + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/select/select.tsx b/packages/@headlessui-react/src/components/select/select.tsx index afcc900fa..82d0653a3 100644 --- a/packages/@headlessui-react/src/components/select/select.tsx +++ b/packages/@headlessui-react/src/components/select/select.tsx @@ -11,7 +11,7 @@ import type { Props } from '../../types' import { forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -89,6 +89,8 @@ function SelectFn( } satisfies SelectRenderPropArg }, [disabled, invalid, hover, focus, active, autoFocus]) + let render = useRender() + return render({ ourProps, theirProps, diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx index 989fe817f..f3c9df671 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -32,7 +32,7 @@ import { attemptSubmit } from '../../utils/form' import { forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type RefProp, } from '../../utils/render' @@ -74,6 +74,8 @@ function GroupFn( let ourProps = {} let theirProps = props + let render = useRender() + return ( ( return onChange?.(defaultChecked) }, [onChange, defaultChecked]) + let render = useRender() + return ( <> {name != null && ( diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index 52c925212..651c2630d 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -33,7 +33,7 @@ import { RenderFeatures, forwardRefWithAs, mergeProps, - render, + useRender, type HasDisplayName, type PropsForFeatures, type RefProp, @@ -320,6 +320,8 @@ function GroupFn( let ourProps = { ref: tabsRef } + let render = useRender() + return ( @@ -384,6 +386,8 @@ function ListFn( 'aria-orientation': orientation, } + let render = useRender() + return render({ ourProps, theirProps, @@ -556,6 +560,8 @@ function TabFn( pressProps ) + let render = useRender() + return render({ ourProps, theirProps, @@ -589,6 +595,8 @@ function PanelsFn( let theirProps = props let ourProps = { ref: panelsRef } + let render = useRender() + return render({ ourProps, theirProps, @@ -650,6 +658,8 @@ function PanelFn( focusProps ) + let render = useRender() + if (!selected && (theirProps.unmount ?? true) && !(theirProps.static ?? false)) { return