Troubleshooting
Common issues and solutions when using @crimson_dev/use-resize-observer.
Width and Height Are Always undefined
The ref is not attached
The most common cause is that the ref returned by the hook is not attached to a DOM element.
// BUG: ref is not used
const { ref, width } = useResizeObserver<HTMLDivElement>();
return <div>{width}</div>; // width is always undefined
// FIX: attach the ref
const { ref, width } = useResizeObserver<HTMLDivElement>();
return <div ref={ref}>{width}</div>;The element is not rendered
If the element is conditionally rendered and not currently in the DOM, the observer has nothing to observe:
// The observer only works when showPanel is true
const { ref, width } = useResizeObserver<HTMLDivElement>();
return showPanel ? <div ref={ref}>{width}</div> : null;TIP
Consider using the onResize callback pattern if you need to handle the case where the element might not be mounted initially. The callback will fire as soon as the element appears in the DOM.
The element has display: none or display: inline
ResizeObserver does not report dimensions for elements with display: none. Inline elements (display: inline) also do not generate resize observations. Ensure the element participates in block layout:
/* Fix: ensure the element is a block-level element */
.observed {
display: block; /* or inline-block, flex, grid */
}Use visibility: hidden or opacity: 0 instead of display: none if you need to hide an element while still measuring it.
Infinite Render Loop
Resizing inside the render
If you change an element's size in response to a resize observation, you can create a feedback loop:
// DANGER: infinite loop
const { ref, width } = useResizeObserver<HTMLDivElement>();
return (
<div ref={ref} style={{ width: width !== undefined ? width + 10 : 'auto' }}>
Content
</div>
);DANGER
Never set the observed element's dimensions directly from the observed values. This creates a feedback loop where each resize triggers another resize. Instead, set dimensions on a child or sibling element:
const { ref, width } = useResizeObserver<HTMLDivElement>();
return (
<div ref={ref}>
<div style={{ width: width !== undefined ? width * 0.8 : 'auto' }}>
Content
</div>
</div>
);SSR: ReferenceError: ResizeObserver is not defined
Importing in server code
The hook must only be used in client components. Ensure your component has 'use client' at the top:
'use client'; // Required for Next.js App Router
import { useResizeObserver } from '@crimson_dev/use-resize-observer';If you need a server-safe import, use the /server entry:
import { createServerResizeObserverMock } from '@crimson_dev/use-resize-observer/server';Worker Mode: SharedArrayBuffer is not defined
Missing COOP/COEP headers
Worker mode requires cross-origin isolation. Ensure your server sends:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpVerify in the browser console:
console.log('crossOriginIsolated:', crossOriginIsolated);
// Must be true for worker modeSee the Worker Mode guide for server configuration examples.
Non-secure context
SharedArrayBuffer requires a secure context (HTTPS or localhost). It will not work over plain HTTP on non-localhost origins.
Worker Mode: Third-Party Resources Blocked
COEP blocking external resources
Cross-Origin-Embedder-Policy: require-corp blocks cross-origin resources that don't include Cross-Origin-Resource-Policy: cross-origin. This affects external images, third-party scripts, and iframes without CORP headers.
Solutions:
Add
crossoriginattribute to external resources:html<img src="https://cdn.example.com/image.jpg" crossorigin="anonymous" />Use
credentiallessinstead ofrequire-corp(Chrome 96+):textCross-Origin-Embedder-Policy: credentiallessOnly enable COOP/COEP on pages that need worker mode, not site-wide.
TypeScript Errors
exactOptionalPropertyTypes and width/height checks
With exactOptionalPropertyTypes: true, you cannot use falsy checks for width and height:
// ERROR with exactOptionalPropertyTypes: width could be 0
if (width) { /* ... */ }
// CORRECT
if (width !== undefined) { /* ... */ }Generic type mismatch
The generic parameter must extend Element:
// ERROR
const { ref } = useResizeObserver<string>();
// CORRECT
const { ref } = useResizeObserver<HTMLDivElement>();
const { ref } = useResizeObserver<SVGSVGElement>();Import errors with TypeScript 6 and isolatedDeclarations
Ensure your tsconfig.json has moduleResolution set to bundler or nodenext:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}If you see errors about isolatedDeclarations, ensure you are on TypeScript 6.0 or later:
npm install typescript@latestdevicePixelContentBoxSize Returns Same Values as contentBoxSize
Cause: The browser does not support devicePixelContentBoxSize (Safari/WebKit).
The hook gracefully falls back to contentBoxSize values (CSS pixels, not device pixels). This is by design -- check browser support before relying on device-pixel precision.
React Compiler Issues
Stale closure in onResize
If your onResize callback captures stale state, the issue is likely in your component code, not in the hook. The hook stabilizes callback identity via a ref pattern, so the latest closure is always invoked.
Do not wrap onResize in useCallback
The hook already stabilizes the callback identity internally. Adding useCallback creates unnecessary memoization overhead:
// UNNECESSARY: double-stabilization
const onResize = useCallback((entry) => { /* ... */ }, []);
useResizeObserver({ onResize });
// CORRECT: just pass the function
useResizeObserver({ onResize: (entry) => { /* ... */ } });Performance Issues
Too many re-renders
If you see excessive re-renders, check whether you are using the reactive return values (width, height) when you only need the callback:
// CAUSES RE-RENDERS: width/height change triggers component re-render
const { ref, width, height } = useResizeObserver<HTMLDivElement>();
useEffect(() => {
drawCanvas(width, height);
}, [width, height]);
// BETTER: use the callback to avoid re-renders entirely
const { ref } = useResizeObserver<HTMLDivElement>({
onResize: (entry) => {
drawCanvas(entry.contentRect.width, entry.contentRect.height);
},
});Measurements lag behind visual state
This is expected behavior. The hook batches updates via requestAnimationFrame and startTransition, meaning measurements are 1-2 frames behind the visual state. For most UI applications this is imperceptible. If you need synchronous measurements, use the onResize callback which fires directly from the observer.
Float16Array is not defined
Cause: Running on a JavaScript engine that does not support ES2026 Float16Array.
Worker mode requires Float16Array. Ensure your runtime supports it:
- Node.js >= 25.0.0 (development/testing)
- Chromium >= 128, Firefox >= 129 (browser runtime)
If Float16Array is not available, worker mode automatically falls back to Float32Array.
Bundle Size Larger Than Expected
Cause: Importing from the wrong entry point or importing worker/server code alongside the main hook.
// CORRECT: tree-shakeable import (1.11 kB gzip)
import { useResizeObserver } from '@crimson_dev/use-resize-observer';Verify with:
npm run sizeMemory Leak Warnings
Unlikely with this library -- the pool uses WeakMap keyed by DOM elements and FinalizationRegistry as a safety net. If you see memory leak warnings, they are likely from another library. Verify by checking whether the leaked reference points to a ResizeObserver instance.
Still Stuck?
- Check the GitHub Issues
- Search for your error message in existing issues
- Open a new issue with a minimal reproduction
- Read the Architecture guide for deeper understanding of internals