Skip to content

Commit 7722b14

Browse files
authored
test/use resize observer func target closure (#226)
* test: add regression case for function target closure * fix: retry function target observe on next render
1 parent 7ae38d2 commit 7722b14

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

src/useResizeObserver.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,15 @@ export default function useResizeObserver(
6464

6565
// Dynamic observe
6666
const isFuncTarget = typeof getTarget === 'function';
67+
const funcTargetIdRef = React.useRef(0);
68+
6769
React.useEffect(() => {
6870
const target = isFuncTarget ? getTarget() : getTarget;
6971

7072
if (target && enabled) {
7173
observe(target, onInternalResize);
74+
} else if (enabled && isFuncTarget) {
75+
funcTargetIdRef.current += 1;
7276
}
7377

7478
return () => {
@@ -78,7 +82,8 @@ export default function useResizeObserver(
7882
};
7983
}, [
8084
enabled,
81-
// When is function, no need to watch it
82-
isFuncTarget ? 0 : getTarget,
85+
// If function target resolves after a parent render, the bumped ref value
86+
// lets the next render re-run this effect without watching the function identity.
87+
isFuncTarget ? funcTargetIdRef.current : getTarget,
8388
]);
8489
}

tests/useResizeObserver.spec.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { render, waitFor } from '@testing-library/react';
2+
import { useEvent } from '@rc-component/util';
3+
import React from 'react';
4+
import { useResizeObserver } from '../src';
5+
import { _el as elementListeners } from '../src/utils/observerUtil';
6+
7+
describe('useResizeObserver', () => {
8+
it('should observe latest element when target getter closes over stateful ref value', async () => {
9+
function Demo() {
10+
const [element, setElement] = React.useState<HTMLElement | null>(null);
11+
// `useEvent` keeps the getter identity stable while the closed-over DOM node
12+
// comes from state. If the hook only checks the function reference, it misses
13+
// the later state update from `null` to the actual element and never observes it.
14+
const getTarget = useEvent(() => element as HTMLElement);
15+
16+
useResizeObserver(true, getTarget);
17+
18+
return <div ref={setElement} data-testid="target" />;
19+
}
20+
21+
const { getByTestId } = render(<Demo />);
22+
const target = getByTestId('target');
23+
24+
await waitFor(() => {
25+
// Once the ref callback stores the DOM into state, the latest element should
26+
// still be observed even though the getter function itself never changes.
27+
expect(elementListeners.get(target)).toBeTruthy();
28+
});
29+
});
30+
});

0 commit comments

Comments
 (0)