Skip to content

Commit

Permalink
Fix ref stealing from children (#1820)
Browse files Browse the repository at this point in the history
* fix ref stealing

When a higher-level component (like `Transition`) provides a `ref` to
its child component, then it will override the `ref` that was
potentially already on the child.

This will make sure that these are merged together correctly.

Fixes: #985

* update changelog
  • Loading branch information
RobinMalfait committed Sep 5, 2022
1 parent 13463f0 commit 3db54db
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `Transition` component's incorrect cleanup and order of events ([#1803](https://github.com/tailwindlabs/headlessui/pull/1803))
- Ensure enter transitions work when using `unmount={false}` ([#1811](https://github.com/tailwindlabs/headlessui/pull/1811))
- Improve accessibility when announcing `Listbox.Option` and `Combobox.Option` components ([#1812](https://github.com/tailwindlabs/headlessui/pull/1812))
- Fix `ref` stealing from children ([#1820](https://github.com/tailwindlabs/headlessui/pull/1820))

## [1.6.6] - 2022-07-07

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ import { Transition } from './transition'

import { executeTimeline } from '../../test-utils/execute-timeline'

function nextFrame() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
resolve()
})
})
})
}

it('should not steal the ref from the child', async () => {
let fn = jest.fn()
render(
<Transition show={true} as={Fragment}>
<div ref={fn}>...</div>
</Transition>
)

await nextFrame()

expect(fn).toHaveBeenCalled()
})

it('should render without crashing', () => {
render(
<Transition show={true}>
Expand Down
17 changes: 16 additions & 1 deletion packages/@headlessui-react/src/utils/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ function _render<TTag extends ElementType, TSlot>(
// Filter out undefined values so that they don't override the existing values
mergeProps(resolvedChildren.props, compact(omit(rest, ['ref']))),
dataAttributes,
refRelatedProps
refRelatedProps,
mergeRefs((resolvedChildren as any).ref, refRelatedProps.ref)
)
)
}
Expand All @@ -193,6 +194,20 @@ function _render<TTag extends ElementType, TSlot>(
)
}

function mergeRefs(...refs: any[]) {
return {
ref: refs.every((ref) => ref == null)
? undefined
: (value: any) => {
for (let ref of refs) {
if (ref == null) continue
if (typeof ref === 'function') ref(value)
else ref.current = value
}
},
}
}

function mergeProps(...listOfProps: Props<any, any>[]) {
if (listOfProps.length === 0) return {}
if (listOfProps.length === 1) return listOfProps[0]
Expand Down

0 comments on commit 3db54db

Please sign in to comment.