@r01al/use-transition-worker
v0.1.2
Published
Run CPU-heavy work in a Web Worker and render the latest result with a React transition.
Maintainers
Readme
useTransitionWorker
useTransitionWorker combines Web Worker computation with React transition
rendering. It returns request controls, result state, and pending indicators:
const { runTransition, isPending, cancel } =
useTransitionWorker(workerHandler);The Worker performs CPU-heavy computation away from the main thread. When the
latest result arrives, the action passed to runTransition runs inside
React's startTransition, so its state updates render at transition priority.
runTransition(input, action)
↓
Web Worker computes result
↓
React startTransition
↓
action(result)
↓
React renders the action's state updateInstallation
npm install @r01al/use-transition-workerReact 18 or newer is required as a peer dependency.
Quick start
Define a self-contained Worker handler and apply its result to component state:
import { useState } from 'react';
import { useTransitionWorker } from '@r01al/use-transition-worker';
type SearchInput = {
items: Array<{ id: number; title: string }>;
query: string;
};
type SearchResult = Array<{ id: number; title: string }>;
function Search({ items }: { items: SearchResult }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult>([]);
const {
runTransition,
error,
isPending,
cancel,
} = useTransitionWorker<
SearchInput,
SearchResult
>(({ items: workerItems, query: workerQuery }) => {
const normalizedQuery = workerQuery.toLowerCase();
return workerItems.filter((item) =>
item.title.toLowerCase().includes(normalizedQuery),
);
});
function handleSearch() {
void runTransition({ items, query }, setResults).catch((error) => {
if (error instanceof Error && error.name === 'AbortError') {
return;
}
console.error(error);
});
}
return (
<>
<input value={query} onChange={(event) => setQuery(event.target.value)} />
<button onClick={handleSearch} disabled={isPending}>
{isPending ? 'Working…' : 'Search'}
</button>
<button onClick={cancel} disabled={!isPending}>
Cancel
</button>
{error && <p>{error.message}</p>}
{results.map((item) => (
<p key={item.id}>{item.title}</p>
))}
</>
);
}The result action can perform more than one synchronous React state update:
void runTransition(input, (result) => {
setResults(result);
setSelectedId(null);
});Awaiting a result
runTransition() returns Promise<Result>. This makes Worker failures
observable and also lets non-rendering code inspect the computed value:
try {
const result = await runTransition(input, setResults);
console.log('Computed', result.length, 'items');
} catch (error) {
console.error(error);
}The promise resolves when the Worker response arrives. React may commit the state update from the result action slightly later.
Pending behavior
isPending is true while either:
- The latest request is computing in the Worker.
- React is rendering the result action's transition.
If the result action does not schedule a React state update, there is no transition render to remain pending after the Worker finishes.
Latest call wins
Calls can overlap:
void runTransition({ query: 'r', items }, setResults);
void runTransition({ query: 're', items }, setResults);
void runTransition({ query: 'react', items }, setResults);Every returned promise resolves with its own Worker result, but only the latest request's result action runs. A stale response therefore cannot replace newer UI state.
Cancellation
cancel() terminates the current Worker, revokes its Blob URL, and rejects all
unfinished request promises with an AbortError:
cancel();Cancelled results cannot run their actions or update hook state. The next call
to runTransition() lazily creates a fresh Worker.
Debugging
Debug logging is disabled by default:
const worker = useTransitionWorker(task, {
debug: true,
});Debug mode logs Worker lifecycle events and request IDs from the main thread and Worker. It never logs input or result values.
API
function useTransitionWorker<Input, Result>(
handler: TransitionWorkerHandler<Input, Result>,
options?: UseTransitionWorkerOptions,
): UseTransitionWorkerValue<Input, Result>;Handler
type TransitionWorkerHandler<Input, Result> = (
input: Input,
) => Result | Promise<Result>;The handler is serialized into the Blob Worker. It must be self-contained and must not capture component variables or imported helpers.
Result action
The result action runs on the main thread inside React's startTransition.
Unlike the Worker handler, it may use component closures and React state
setters. State updates must be made synchronously within the action to inherit
the transition priority.
Return value
type UseTransitionWorkerValue<Input, Result> = {
runTransition: (
input: Input,
action: (result: Result) => void,
) => Promise<Result>;
result: Result | null;
error: TransitionWorkerError | null;
isComputing: boolean;
isRendering: boolean;
isPending: boolean;
cancel: () => void;
};result contains the latest successful non-stale result, while error
contains the latest non-stale Worker error. Starting another request clears
error; cancellation leaves the previous successful result available.
Options
type UseTransitionWorkerOptions = {
debug?: boolean;
};Worker function rules
A Worker has a different global environment from the React component. The handler cannot access:
- React hooks or state setters.
- Component closures.
- Imported helpers.
- The DOM,
window, ordocument.
Pass everything required by the calculation through runTransition(input,
action) or define helpers inside the handler. Inputs and results must support
the browser's structured clone algorithm.
Browser and security requirements
The browser must support Web Workers, Blob URLs, Promises, and the structured clone algorithm. Because the Worker is created from a Blob URL, the application's Content Security Policy must allow:
worker-src blob:Why both a Worker and useTransition?
React transitions do not move computation to another thread; they only change the priority of React state updates. This hook uses a Worker for parallel computation, then uses React's transition for rendering the returned result.
