Skip to content

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:

typescript
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 /core entry

Explicit Resource Management ​

The factory supports ES2026 using declarations for automatic cleanup:

typescript
{
  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:

typescript
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:

tsx
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:

tsx
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:

tsx
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 ​

mermaid
flowchart TD
    CTX["ResizeObserverContext\nvalue={MockResizeObserver}"]
    HOOK["useResizeObserver()"]
    POOL["ObserverPool"]
    MOCK["MockResizeObserver instance"]

    CTX --> HOOK
    HOOK --> |"use(ResizeObserverContext)"| POOL
    POOL --> |"new Constructor(callback)"| MOCK

When 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:

typescript
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 ResizeObserver wrapper with EventTarget dispatching
  • EventTarget interface for standard event handling
  • No React dependency (can be used in any JS environment)

TypeScript types ​

typescript
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:

typescript
import { sumPrecise } from '@crimson_dev/use-resize-observer/shim';

const total = sumPrecise([0.1, 0.2, 0.3]);
// Exact sum without intermediate rounding error

Internal 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:

tsx
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 ​

APIEntry PointFrameworkUse Case
useResizeObserverMainReactStandard hook
useResizeObserverEntriesMainReactMulti-element observation
createResizeObserverMainAnyFactory / vanilla JS
ResizeObserverContextMainReactDI / testing / SSR
createResizeObservable/coreAnyEventTarget-based observable
useResizeObserverWorker/workerReactSAB-based measurement sharing
createServerResizeObserverMock/serverReactSSR/RSC mock
sumPrecise/shimAnyDrift-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

Released under the MIT License.