Advanced API
Beyond the primary useResizeObserver hook, the library provides several advanced APIs for specialized use cases.
createResizeObserver Factory
A framework-agnostic factory that uses the same shared pool as the React hook:
import { createResizeObserver } from '@crimson_dev/use-resize-observer';
const observer = createResizeObserver({ box: 'border-box' });
observer.observe(element, (entry) => {
console.log(entry.contentRect.width, entry.contentRect.height);
});
// Stop observing one element
observer.unobserve(element, callback);
// Disconnect all observations
observer.disconnect();Use Cases
- Vanilla JS applications without React
- Web Components that need resize tracking
- Canvas libraries (Three.js, PixiJS) managing their own render loop
- Non-React frameworks (Solid, Vue, Svelte) via the
/coreentry
Explicit Resource Management
The factory supports ES2026 using declarations for automatic cleanup:
{
using observer = createResizeObserver({ box: 'content-box' });
observer.observe(element, (entry) => {
// Handle resize
});
// observer[Symbol.dispose]() called automatically at end of block
}This is particularly useful in test setups where you want guaranteed cleanup:
test('element resizes correctly', () => {
using observer = createResizeObserver();
// observer is disposed after test, even if assertions throw
});useResizeObserverEntries -- Multi-Element
Observe multiple elements simultaneously with a single hook call:
import { useRef } from 'react';
import { useResizeObserverEntries } from '@crimson_dev/use-resize-observer';
const Dashboard = () => {
const panel1 = useRef<HTMLDivElement>(null);
const panel2 = useRef<HTMLDivElement>(null);
const panel3 = useRef<HTMLDivElement>(null);
const entries = useResizeObserverEntries([panel1, panel2, panel3]);
return (
<div className="grid">
<div ref={panel1}>
Panel 1: {entries.get(panel1.current!)?.width ?? 'measuring'}
</div>
<div ref={panel2}>
Panel 2: {entries.get(panel2.current!)?.width ?? 'measuring'}
</div>
<div ref={panel3}>
Panel 3: {entries.get(panel3.current!)?.width ?? 'measuring'}
</div>
</div>
);
};Performance
useResizeObserverEntries uses a single pool subscription for all elements -- not N separate subscriptions. This is more efficient than calling useResizeObserver N times when you need to track many elements from the same component.
Dynamic ref lists
The hook handles dynamic ref arrays. Elements can be added or removed between renders:
const DynamicList = ({ items }: { items: string[] }) => {
const refs = items.map(() => useRef<HTMLDivElement>(null));
const entries = useResizeObserverEntries(refs);
return (
<div>
{items.map((item, i) => (
<div key={item} ref={refs[i]}>
{item}: {entries.get(refs[i].current!)?.width ?? '...'}px
</div>
))}
</div>
);
};Rules of Hooks
The example above calls useRef in a loop, which works only if the list length is stable between renders. For truly dynamic lists, use a single Map<string, RefObject> pattern instead.
ResizeObserverContext -- Dependency Injection
Inject a custom ResizeObserver constructor for testing, SSR, or polyfill scenarios:
import { ResizeObserverContext } from '@crimson_dev/use-resize-observer';
// Mock for tests that immediately reports a fixed size
const MockResizeObserver = class {
constructor(private callback: ResizeObserverCallback) {}
observe(target: Element) {
this.callback(
[{
target,
contentBoxSize: [{ inlineSize: 500, blockSize: 300 }],
borderBoxSize: [{ inlineSize: 500, blockSize: 300 }],
contentRect: new DOMRectReadOnly(0, 0, 500, 300),
devicePixelContentBoxSize: [],
} as ResizeObserverEntry],
this as unknown as ResizeObserver,
);
}
unobserve() {}
disconnect() {}
};
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<ResizeObserverContext value={MockResizeObserver as unknown as typeof ResizeObserver}>
{children}
</ResizeObserverContext>
);How the context flows
flowchart TD
CTX["ResizeObserverContext\nvalue={MockResizeObserver}"]
HOOK["useResizeObserver()"]
POOL["ObserverPool"]
MOCK["MockResizeObserver instance"]
CTX --> HOOK
HOOK --> |"use(ResizeObserverContext)"| POOL
POOL --> |"new Constructor(callback)"| MOCKWhen a context value is provided, the pool uses that constructor instead of the global ResizeObserver. Each unique constructor gets its own pool instance to prevent cross-contamination.
createResizeObservable -- Framework-Agnostic Core
The /core entry provides an EventTarget-based observable for use outside React:
import { createResizeObservable } from '@crimson_dev/use-resize-observer/core';
const observable = createResizeObservable(element, { box: 'content-box' });
observable.addEventListener('resize', (event) => {
const { width, height } = (event as CustomEvent).detail;
console.log(`${width} x ${height}`);
});
// Cleanup
observable.disconnect();This is the lowest-level API in the library. It provides:
- A standalone
ResizeObserverwrapper withEventTargetdispatching EventTargetinterface for standard event handling- No React dependency (can be used in any JS environment)
TypeScript types
interface ResizeEventDetail {
readonly width: number;
readonly height: number;
readonly entry: ResizeObserverEntry;
}
interface ResizeObservable extends EventTarget, Disposable {
disconnect(): void;
}Precise Summation (/shim)
The /shim entry exports sumPrecise, a helper for accumulating sub-pixel measurements without floating-point drift. It uses Math.sumPrecise() (ES2026) when the runtime supports it, falling back to iterative addition:
import { sumPrecise } from '@crimson_dev/use-resize-observer/shim';
const total = sumPrecise([0.1, 0.2, 0.3]);
// Exact sum without intermediate rounding errorInternal device-pixel helpers
Device-pixel rounding helpers (roundToDevicePixel, normalizeDimension) live in the internal src/shim/wasm-round.ts module, backed by an optional AssemblyScript WASM build (wasm/round.ts). They are @internal and not part of the public API — for device-pixel-accurate sizes, use the device-pixel-content-box box model, which reports physical pixels directly.
Shadow DOM Support
The hook supports observation within Shadow DOM by scoping the pool per shadow root:
const ShadowComponent = ({ shadowRoot }: { shadowRoot: ShadowRoot }) => {
const { ref, width } = useResizeObserver<HTMLDivElement>({
root: shadowRoot,
});
return <div ref={ref}>{width}px</div>;
};Each ShadowRoot gets its own ObserverPool instance, preventing cross-contamination between shadow trees. The pool registry is a WeakMap keyed by root, so when a shadow root is detached and garbage collected, its pool goes with it — no manual cleanup required.
API Summary
| API | Entry Point | Framework | Use Case |
|---|---|---|---|
useResizeObserver | Main | React | Standard hook |
useResizeObserverEntries | Main | React | Multi-element observation |
createResizeObserver | Main | Any | Factory / vanilla JS |
ResizeObserverContext | Main | React | DI / testing / SSR |
createResizeObservable | /core | Any | EventTarget-based observable |
useResizeObserverWorker | /worker | React | SAB-based measurement sharing |
createServerResizeObserverMock | /server | React | SSR/RSC mock |
sumPrecise | /shim | Any | Drift-free float summation |
Next Steps
- Examples -- Real-world usage patterns for each API
- Worker Mode -- Deep dive on
useResizeObserverWorker - SSR & RSC -- Server-side usage with
ResizeObserverContext