Fix ref stealing from children (#1820)

* 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
This commit is contained in:
Robin Malfait
2022-09-05 13:09:23 +02:00
committed by GitHub
parent 13463f0295
commit 3db54db1ba
3 changed files with 40 additions and 1 deletions
+1
View File
@@ -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
@@ -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}>
+16 -1
View File
@@ -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)
)
)
}
@@ -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]