react-webworker: a React handle on a Web Worker or Service Worker
⚙️ Communicate with a Web Worker from React
At a glance
- What is it?
- react-webworker is a zero dependency React component that owns a Web Worker for you and exposes incoming messages, errors and a postMessage function through render props or Context helper components. It saves the boilerplate of wiring a worker into a component tree, and it gives up control of the worker lifecycle the moment you supply your own instance.
- Who is it for?
- Adopt react-webworker when a React app needs to stream results from a worker into the component tree and you want messages, errors and timestamps handled for you without writing an effect.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A component that owns a worker so the tree does not have to
Talking to a Web Worker from plain JavaScript is easy. Talking to one from React is where the friction appears: the worker is created imperatively, its events fire outside React's lifecycle, and every message has to be funnelled into state by hand.
react-webworker removes that bookkeeping. The README describes it as a React component for easy communication with a Web Worker, and it works with Service Workers as well. The package has no runtime dependencies at all, and the only peer requirement is `react` at version 16.3.0 or later, a floor that exists because the helper components use the Context API.
The API splits into two styles, and the README is deliberate about which to pick. The render props form gives you everything in one callback and suits a component that wants full control over how state maps to output. The helper components, `WebWorker.Pending`, `WebWorker.Data` and `WebWorker.Error`, suit the common case where you just want to render one thing while waiting, one thing when data arrives and one thing when something breaks, and the README notes that these helpers do not have to be direct children of `<WebWorker>`.
Underneath, the library keeps a history. `messages` and `errors` are arrays in chronological order, each entry carrying a date, and there are shortcuts for the last message data, the last error, and two timestamps called `updatedAt` and `lastPostAt`. That history is what turns a fire-and-forget worker into something a dashboard can show.
What crosses the bridge, and what the component keeps
The props list is short and specific. `url` is the public URL of the worker file, or a path relative to the domain root, and it is required unless you pass a worker instance. `options` is an object handed to the Worker constructor. `parser` transforms incoming message data but not errors, and `serializer` transforms the payload before `postMessage` sends it, so a custom wire format does not have to be applied at every call site.
Two callback props, `onMessage` and `onError`, are invoked with the message data and the error object respectively, which lets you log or forward events without going through the render path.
The render props expose `data`, which is the last received message data and is maintained even when an error is received, and `error`, the last received error, which is cleared when a new message arrives. That asymmetry is deliberate and worth knowing: an error does not wipe the data you already have, while a new message clears the error.
There is a practical caveat on `url` and `options`: the README states they are evaluated at mount time, so they must be defined immediately and will not respond to changes. A worker URL that depends on a prop, a query parameter or a feature flag cannot be swapped after the first render through those props.
The README also recommends sending and receiving JSON strings rather than structured values, citing a note on high performance web worker messages, which keeps the serialization cost predictable on the worker side.
Installing the package and rendering the first message
Installation is one command:
npm install --save react-webworkerThe render props version puts the worker state directly in your markup:
import WebWorker from "react-webworker"
const MyComponent = () => (
<WebWorker url="/worker.js">
{({ data, error, postMessage }) => {
if (error) return `Something went wrong: ${error.message}`
if (data)
return (
<div>
<strong>Received some data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
return <button onClick={() => postMessage("hello")}>Hello</button>
}}
</WebWorker>
)What you should see on screen is a button until the worker replies, then the payload rendered as formatted JSON, and an error string instead if the worker failed.
The helper component style covers the same ground without a single large callback:
import WebWorker from "react-webworker"
const MyComponent = () => (
<WebWorker url="/worker.js">
<WebWorker.Pending>
{({ postMessage }) => <button onClick={() => postMessage("hello")}>Hello</button>}
</WebWorker.Pending>
<WebWorker.Data>
{data => (
<div>
<strong>Received some data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</WebWorker.Data>
<WebWorker.Error>{error => `Something went wrong: ${error.message}`}</WebWorker.Error>
</WebWorker>
)Note that `postMessage` is available inside the pending helper as well, so the same button can send a message before any data has arrived.
Handing in your own worker and losing the lifecycle
The `worker` prop is where this library changes character. Instead of creating a Worker from `url`, you can pass an existing instance, and `url` and `options` are then ignored.
The README gives the reason: Parcel and worker-plugin allow the worker script to be bundled automatically, but only when you create the Worker instance yourself rather than letting react-webworker do it.
import WebWorker from "react-webworker"
const myWorker = new Worker("./worker.js") // relative path to the source file, not the public URL
const MyComponent = () => <WebWorker worker={myWorker}>...</WebWorker>The path in that snippet is the source file for the bundler, not the public URL, which is exactly the kind of detail that breaks when the two are confused.
The cost is stated plainly: with a custom worker, `<WebWorker>` will not manage the Worker's lifecycle, and it will not terminate the worker when the component unmounts. The README repeats the point for the `worker` prop, saying you will have to manage the lifecycle yourself. In a long-lived single page app that navigates away and back, that means either a worker you leak on every mount or a teardown routine you write yourself.
The same trade-off appears with Service Workers. Passing `navigator.serviceWorker` as the worker instance is enough to get started, and the component sets up a MessageChannel for bidirectional communication:
const MyComponent = () => <WebWorker worker={navigator.serviceWorker}>...</WebWorker>The Service Worker side then reads the port instead of posting to the global scope:
// `ports` is automatically provided with a MessageChannel port
self.onmessage = ({ data, ports: [port] }) => {
console.log("inside the service worker:", data)
port.postMessage(data) // instead of `self.postMessage`
}The README warns that messages sent to an inactive, not activated, Service Worker are silently ignored, and that you have to deal with the Service Worker lifecycle yourself. Silent failure is the worst kind for a data pipeline, because nothing throws and the UI simply never updates.
Where the abstraction stops paying
The first limit is the mount time evaluation of `url` and `options`. Any design where the worker script or its options depend on runtime state has to work around it, either by remounting the component with a changing key or by creating the worker outside React and passing it in, which trades this limit for the lifecycle limit.
The second limit is the scope of the error channel. `errors` collects errors and `onError` fires for them, but the library does not model a worker that died, a message that was malformed, or a payload that the `parser` threw on. The README documents no retry, no timeout and no backpressure. If your worker can stall, the component has no opinion about it.
The third limit is framework coupling. The package declares `react` as a peer dependency and uses the Context API, so this is a React solution rather than a framework neutral one. Its own README says the package was modeled after React Async, which helps you deal with Promises in React, so the intended mental model is component shaped rather than event shaped.
Finally, the surface is small on purpose. Seven props, six render props, three helper components. There is no plugin system, no middleware and no state store, which is a virtue in a small dependency and a limit when you want to route messages through a Redux or Zustand store of your own.
Licence, build pipeline and an old tag
react-webworker is published under the ISC licence, with a LICENSE file at the top of the repository and a licence badge in the README. ISC is a short permissive licence that allows use, modification and redistribution with the copyright notice retained. Nothing in the package requires a commercial agreement or a per-seat fee.
The packaging is minimal. `main` points at `lib/index.js` and the published files are `lib` and `src`, so the source travels with the package. The build is a single Babel pass, `babel src -d lib`, and the test command is `jest src` with coverage collection enabled. The Babel setup is old: `babel-cli` 6.26.0 with `babel-preset-env` 1.7.0 and stage-2 preset, and the test dependencies pin React 16.4.2 and jest 23.5.0.
That vintage matters more than it looks. The peer range of `react` at 16.3.0 or later permits modern React versions, but the toolchain that produces the published `lib` output has not moved in years, and the `devDependencies` show React 16 as the reference environment rather than React 18 or 19.
The release history shows a single tag, v2.0.0 from 2018-09-13, while `package.json` declares version 2.1.0, and the README advertises Service Worker support as new in v2.1 and custom worker instances as new in v2. So the npm version and the tagged release have drifted apart. The repository is not archived and the last push was on 2026-10-15, and the badges still point at the ghengeveld/react-webworker repository URL rather than the organisation path.
Editorial conclusion
Adopt react-webworker when a React app needs to stream results from a worker into the component tree and you want messages, errors and timestamps handled for you without writing an effect. Do not adopt it when the worker must react to changing props, when the component unmounting has to kill the worker, or when the Service Worker may be inactive, since the README states that url and options are read only at mount time, that a worker passed through the worker prop is never terminated on unmount, and that messages to an inactive Service Worker are silently ignored. Before shipping, decide whether you will let the component create the worker or hand it one through the worker prop, because only the first path gets automatic cleanup.
Frequently asked questions
How do I install react-webworker?
Run `npm install --save react-webworker`. The package has no runtime dependencies and declares `react` at 16.3.0 or later as a peer dependency.
Why does my Web Worker message not appear when I change the url prop?
The README states that `url` and `options` are evaluated at mount time, so they must be defined immediately and will not respond to changes. To use a different worker script later you need to remount the component or pass your own worker instance.
Is the Web Worker terminated when the react-webworker component unmounts?
Only when react-webworker created the worker itself. A custom Worker passed through the `worker` prop is not terminated on unmount, and the README says you have to manage its lifecycle yourself.
How do I communicate with a Service Worker using react-webworker?
Pass `navigator.serviceWorker` as the `worker` prop. The component sets up a MessageChannel, so the worker receives a port and replies with `port.postMessage(data)` rather than `self.postMessage`.
What happens if I send a message to a Service Worker that is not activated?
The README states that messages sent to an inactive Service Worker are silently ignored. You have to deal with the Service Worker lifecycle yourself, so nothing will appear on screen and no error is raised.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/async-library-react-webworker)Was this useful?