diff --git a/src/useResizeObserver.ts b/src/useResizeObserver.ts index cf5b40d..c0be320 100644 --- a/src/useResizeObserver.ts +++ b/src/useResizeObserver.ts @@ -64,11 +64,15 @@ export default function useResizeObserver( // Dynamic observe const isFuncTarget = typeof getTarget === 'function'; + const funcTargetIdRef = React.useRef(0); + React.useEffect(() => { const target = isFuncTarget ? getTarget() : getTarget; if (target && enabled) { observe(target, onInternalResize); + } else if (enabled && isFuncTarget) { + funcTargetIdRef.current += 1; } return () => { @@ -78,7 +82,8 @@ export default function useResizeObserver( }; }, [ enabled, - // When is function, no need to watch it - isFuncTarget ? 0 : getTarget, + // If function target resolves after a parent render, the bumped ref value + // lets the next render re-run this effect without watching the function identity. + isFuncTarget ? funcTargetIdRef.current : getTarget, ]); } diff --git a/tests/useResizeObserver.spec.tsx b/tests/useResizeObserver.spec.tsx new file mode 100644 index 0000000..ad1f6e0 --- /dev/null +++ b/tests/useResizeObserver.spec.tsx @@ -0,0 +1,30 @@ +import { render, waitFor } from '@testing-library/react'; +import { useEvent } from '@rc-component/util'; +import React from 'react'; +import { useResizeObserver } from '../src'; +import { _el as elementListeners } from '../src/utils/observerUtil'; + +describe('useResizeObserver', () => { + it('should observe latest element when target getter closes over stateful ref value', async () => { + function Demo() { + const [element, setElement] = React.useState(null); + // `useEvent` keeps the getter identity stable while the closed-over DOM node + // comes from state. If the hook only checks the function reference, it misses + // the later state update from `null` to the actual element and never observes it. + const getTarget = useEvent(() => element as HTMLElement); + + useResizeObserver(true, getTarget); + + return
; + } + + const { getByTestId } = render(); + const target = getByTestId('target'); + + await waitFor(() => { + // Once the ref callback stores the DOM into state, the latest element should + // still be observed even though the getter function itself never changes. + expect(elementListeners.get(target)).toBeTruthy(); + }); + }); +});