useWebWorkerGitHub

React hooks for Web Workers

Move CPU-heavy work out of React's render lane.

useWebWorker gives React apps a small function-style API for running expensive work in a Web Worker, with TypeScript inference, status tracking, timeout handling, and automatic cleanup.

pnpm add @atom-universe/use-web-worker
1.86 KBminified bundle
0 depsReact peer only
TS firsttyped hook return
quick-start.tsx
import { useWebWorkerFn } from '@atom-universe/use-web-worker';

function App() {
  const [workerFn, status] = useWebWorkerFn(
    (items: number[]) => items.reduce((total, item) => total + item, 0)
  );

  async function calculate() {
    const result = await workerFn([1, 2, 3]);
    console.log(result);
  }

  return <button onClick={calculate}>Run in worker: {status}</button>;
}

Why workers

Workers are not about making math faster. They keep the UI responsive.

Function-like calls

Call a worker function and await the result without maintaining worker files by hand.

Progress messages

Use the injected worker context to stream progress updates back to React state.

Lifecycle cleanup

The hook terminates generated workers on completion, timeout, error, or unmount.

Benchmark

Measured responsiveness, not just total runtime.

The reproducible script runs a Mandelbrot-style workload on the main thread and in a worker thread. Total runtime can be similar, but the worker path keeps frame scheduling available for the page.

Interactive comparison

Generate the same fractal on each side.

The left panel runs a heavier Mandelbrot-style stress test on the main thread. The right panel uses a Web Worker, so the pulse indicator should keep moving while the canvas is generated.

Blocking path

Main thread

Blocks the UI while computing.

Canvas waits for generation
Elapsed--
Max gap--
Missed frames--
Responsive path

Web Worker

Keeps the page responsive.

Canvas waits for generation
Elapsed--
Max gap--
Missed frames--
Stress workload: 520 x 360, 760 max iterationsRun both panels to compare checksums

Reproducible script result

Same workload, measured from a repeatable local script.

Max timer gap reduction91.08%

Lower timer gaps mean the main thread has more room to paint and respond to input.

Main thread gap193.99 ms

11 missed 60Hz frames in the benchmark run.

Worker path gap17.31 ms

0 missed 60Hz frames in the benchmark run.

ScenarioTotal timeMax timer gapMissed frames
Main thread188.22 ms193.99 ms11
Worker thread195.94 ms17.31 ms0

Generated by pnpm benchmark on Node v22.22.0. Checksum matched: yes.

API quick reference

The current API surface is intentionally small.

useWebWorkerFn

api.ts
const [run, status, terminate] = useWebWorkerFn(fn, {
  dependencies,
  localDependencies,
  timeout,
  onError,
  onMessage,
});

WorkerStatusType

PENDING4

Idle or reset state

RUNNING3

Worker is executing

SUCCESS0

Last run completed

ERROR1

Worker reported an error

TIMEOUT2

Timeout guard fired

localDependencies accepts functions that are stringified into the generated worker. onMessage receives custom worker messages.

Examples

Progress messages are plain worker messages.

progress-worker.ts
export function computeMandelbrot(
  width: number,
  height: number,
  maxIterations: number,
  workerContext: Worker
) {
  workerContext.postMessage(['PROGRESS', { percent: 25 }]);
  return new Array(width * height).fill(maxIterations);
}

Known TODO

Runtime follow-up is tracked separately.

The documentation now reflects the current API. A later core pass should address blob URL cache keys, synchronous concurrent calls, cancellation semantics, and behavior-level hook tests.