React Integration
Problem
A React component needs to offload expensive computation to a worker pool. The pool must be created once, survive re-renders, and be closed when the component unmounts.
Solution
Off-load processing from React components without blocking renders:
tsx
import { useEffect, useMemo, useState } from 'react';
import { createWorker } from '@vielzeug/workit';
type SortInput = { data: number[] };
type SortOutput = { sorted: number[] };
export function SortedList({ data }: { data: number[] }) {
const [sorted, setSorted] = useState<number[]>([]);
const [pending, setPending] = useState(false);
const [error, setError] = useState<Error | null>(null);
// Create worker once and dispose on unmount
const worker = useMemo(
() => createWorker<SortInput, SortOutput>(({ data }) => ({ sorted: [...data].sort((a, b) => a - b) })),
[],
);
useEffect(() => {
return () => worker.dispose();
}, [worker]);
// Trigger sort on data change
useEffect(() => {
let cancelled = false;
setError(null);
setPending(true);
worker
.run({ data })
.then(({ sorted }) => {
if (!cancelled) setSorted(sorted);
})
.catch((err) => {
if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
})
.finally(() => {
if (!cancelled) setPending(false);
});
return () => {
cancelled = true;
};
}, [data, worker]);
if (error) return <p style={{ color: 'red' }}>Error: {error.message}</p>;
if (pending) return <p>Sorting…</p>;
return (
<ul>
{sorted.map((n) => (
<li key={n}>{n}</li>
))}
</ul>
);
}- Worker is properly disposed on component unmount.
- Errors are caught and displayed.
- Pending state is correctly managed through cancellation tokens.
Pitfalls
- Creating
createWorkerPoolinside the component body (not inuseReforuseEffect) spawns new Worker threads on every render. poolRef.currentisnullbetweenuseEffectcleanup and the next setup. Guard calls withpoolRef.current?.run()rather than the non-null assertionpoolRef.current!.run().- Workers do not share the main thread's module scope. Functions passed to the worker run in isolation — avoid closures that capture main-thread variables, as they are serialized and lose their references.
- Creating workers without memoization causes unnecessary recreations.