Skip to content

React Integration ​

Problem ​

A worker pool must survive renders while obsolete component work stops during effect cleanup. React Strict Mode must not dispose and then reuse the same pool during development effect replay.

Solution ​

Create and dispose the pool at the application boundary, then inject it into components. Component effects own only their individual task signals.

tsx
import { useEffect } from 'react';
import { createWorker, type WorkerPool } from '@vielzeug/familiar';

type SortInput = { data: number[] };
type SortOutput = number[];

type SortedListProps = {
  data: number[];
  onError(error: unknown): void;
  pool: WorkerPool<SortInput, SortOutput>;
  setSorted(value: SortOutput): void;
};

function SortedList({ data, onError, pool, setSorted }: SortedListProps) {
  useEffect(() => {
    const controller = new AbortController();

    void pool.run({ data }, { signal: controller.signal }).then(setSorted, (error: unknown) => {
      if (!(error instanceof DOMException && error.name === 'AbortError')) onError(error);
    });

    return () => controller.abort();
  }, [data, onError, pool, setSorted]);

  return null;
}

const pool = createWorker<SortInput, SortOutput>(new URL('./sort.worker.ts', import.meta.url));
root.render(<SortedList data={data} onError={reportError} pool={pool} setSorted={setSorted} />);

// At the same application boundary that owns `root`:
function disposeApplication() {
  root.unmount();
  pool.dispose();
}

Pitfalls ​

  • Do not create and dispose a shared pool in a React effect that Strict Mode can replay.
  • Keep the worker URL and injected pool stable for the owner lifetime.
  • Treat AbortError as expected stale-work cancellation.
  • Dispose the pool only at the boundary that created it.