Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/useResizeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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,
]);
}
30 changes: 30 additions & 0 deletions tests/useResizeObserver.spec.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLElement | null>(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);
Comment on lines +11 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "\.(tsx?|jsx?)$" | head -20

Repository: react-component/resize-observer

Length of output: 479


🏁 Script executed:

fd -e tsx -e ts -e jsx -e js | grep -i "use.*resize" | head -20

Repository: react-component/resize-observer

Length of output: 132


🏁 Script executed:

fd "useResizeObserver" --type f

Repository: react-component/resize-observer

Length of output: 132


🏁 Script executed:

find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "export.*useResizeObserver" | head -10

Repository: react-component/resize-observer

Length of output: 117


🏁 Script executed:

cat -n tests/useResizeObserver.spec.tsx | head -40

Repository: react-component/resize-observer

Length of output: 1558


🏁 Script executed:

cat -n src/useResizeObserver.ts

Repository: react-component/resize-observer

Length of output: 3508


🏁 Script executed:

cat -n src/index.tsx

Repository: react-component/resize-observer

Length of output: 2831


类型签名与运行时实现不匹配,导致需要不安全的类型断言。

测试中 getter 函数初始返回 null(当 element 状态为 null),后来返回真实元素。但当前签名只允许 () => HTMLElement,强制测试使用 as HTMLElement 绕过类型检查。

实现已在第 72 行明确处理了 null 目标(if (target && enabled)),包括第 74-75 行对函数目标 null 情况的特殊处理。既然运行时支持这个场景,公开类型应同步允许 HTMLElement | null() => HTMLElement | null

建议的修改
 export default function useResizeObserver(
   enabled: boolean,
-  getTarget: HTMLElement | (() => HTMLElement),
+  getTarget: HTMLElement | null | (() => HTMLElement | null),
   onDelayResize?: OnResize,
   onSyncResize?: OnResize,
 ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/useResizeObserver.spec.tsx` around lines 11 - 16, The test uses a
getter getTarget and currently asserts it returns HTMLElement, but the runtime
and useResizeObserver(handle in function useResizeObserver) already accept null
targets; update the public type signatures so the target and getter types allow
null (e.g., accept HTMLElement | null and () => HTMLElement | null) and change
the test to remove the unsafe as HTMLElement cast by using the nullable getter
type; ensure symbols referenced are getTarget (test getter) and
useResizeObserver (hook) so callers and tests align with the runtime
null-handling branches (check the conditional handling around target && enabled
and the special function-target null handling).


return <div ref={setElement} data-testid="target" />;
}

const { getByTestId } = render(<Demo />);
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();
});
});
});