From 9938c0dfe2b010a7b020dd356a5ea9494c772bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Tue, 24 Mar 2026 17:10:24 +0800 Subject: [PATCH 1/2] test: add regression case for function target closure --- tests/useResizeObserver.spec.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/useResizeObserver.spec.tsx 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(); + }); + }); +}); From c8166b051f2b597a4e965980e805446be6a2a722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Tue, 24 Mar 2026 17:20:48 +0800 Subject: [PATCH 2/2] fix: retry function target observe on next render --- src/useResizeObserver.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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, ]); }