Library / SDK
alewin/useWorker avatar
alewin/useWorker

useWorker: run expensive functions off the React render thread

⚛️ useWorker() - A React Hook for Blocking-Free Background Tasks

3,112 stars106 forksJavaScriptMIT

At a glance

What is it?
A React hook that moves a function you already wrote into a web worker, so the main thread keeps painting while it runs. It is small, MIT licensed, and deliberately aimed at projects that cannot touch their webpack config.
Who is it for?
Adopt useWorker if you have a Create React App project with a genuinely expensive pure function and no ability to change the bundler, and you have read the limitations page before writing the function. Do not adopt it if your work is I/O bound, needs a pool of workers, or calls closure variables, because the hook moves the function body to a worker and those references will not survive.
Can I use it commercially?
Yes. MIT 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 119 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 September 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem useWorker targets: Create React App and no worker config

The README is direct about the origin of this library. Most React projects are initialized through Create React App, and CRA does not offer support for web workers unless you eject and edit the webpack configuration by hand. useWorker exists so you can use web workers without changing that configuration, and the README admits this constraint is why the library carries limitations and workarounds.

That framing matters when you evaluate it. This is not a general purpose worker abstraction competing on raw capability. It is a hook that fits inside a build setup you do not control. The intended user is a React developer with an expensive synchronous function, usually sorting, parsing or numeric work, who cannot or will not eject. The README points anyone willing to manage workers manually at worker-loader instead, which is an honest sign of where the project thinks its boundary is.

The API surface reflects the same narrowness. You hand the hook a function, you get back a worker handle, and calling that handle returns a promise. The README lists the pitch points: run expensive functions without blocking the UI, promises instead of event messages, under 3KB, TypeScript support, a garbage collected worker instance, remote dependencies and a timeout option.

How the hook moves your function into a worker

The mechanism is the interesting part, and it is also the source of the project's most reported bug. The README states the approach plainly: the library moves the entire function passed to the hook into a worker. It does not ask you to write a separate worker file, and it does not use a bundler loader. It serializes the function source and reconstructs it inside the worker scope.

That explains why the API is promise based. Instead of you writing onmessage handlers and postMessage calls, the hook wraps the message round trip and resolves a promise when the worker replies. It also explains the garbage collector claim: the hook owns the worker instance and cleans it up, so you are not manually terminating workers on unmount.

The consequences of moving function source are concrete. Anything the function closes over is not in the worker scope, because only the function body travels. The README's known issues section describes a related failure: transpiling tools such as Babel can produce "Not refereced" errors, because variable definitions introduced by the transpiler may fall out of scope once the function is moved. The suggested workaround is to declare the function through a Function constructor with the body as a string.

The repository layout shows a pnpm workspace with packages/ and apps/ directories, a changeset setup for versioning, and biome for linting. That structure tells you the release flow is automated, but it does not change the runtime model: one function, one worker, one promise.

Install and first sort in a React component

The README gives the install command and the import path. The package is published under a scoped name, and the hook and a status enum come from the same entry point.

bash
npm install --save @koale/useworker
jsx
import { useWorker, WORKER_STATUS } from "@koale/useworker";

The README's usage example builds a large array of random numbers, defines a plain sort function, and passes that function to the hook. The returned array's first element is the worker caller, and awaiting it gives the result.

jsx
const numbers = [...Array(5000000)].map((e) => ~~(Math.random() * 1000000));
const sortNumbers = (nums) => nums.sort();

const Example = () => {
  const [sortWorker] = useWorker(sortNumbers);

  const runSort = async () => {
    const result = await sortWorker(numbers);
    console.log(result);
  };

  return (
    <button type="button" onClick={runSort}>
      Run Sort
    </button>
  );
};

The pattern to notice is that sortNumbers is defined at module scope, not inside the component. That is not stylistic. A function defined inside the component and referencing props or state would close over values the worker cannot see. The README's examples and live demos, including the sorting and CSV demos, follow the same shape. If your first attempt throws a reference error, the README's Function constructor workaround is the documented escape hatch, and it is worth trying before you file anything.

Where useWorker is the wrong tool

The library is described in its own README as experimental, and the limitations are structural rather than incidental.

First, the closure problem. Because the whole function is moved, a function that reads component state, a ref, or a module variable that the worker cannot resolve will fail. You have to pass everything through arguments. That is a real design constraint, not a documentation gap, and it pushes complexity back into your call sites.

Second, the transpiler interaction. The README devotes a known issues section to Babel producing reference errors, with a workaround that makes the function a string. A workaround that turns your function into a string also removes type checking and editor support for that function, which is a steep price in a TypeScript project.

Third, the missing pieces are visible in the roadmap. A useWorkers hook for a pool and a useWorkerFile hook for file based workers are listed as unchecked items. If your workload needs a pool of workers rather than one worker per hook call, the roadmap says that does not exist yet.

Finally, workers do not help I/O bound work. If you are waiting on a network request, the main thread is not blocked in the first place, and the serialization cost of moving data into and out of a worker can make things slower. The hook targets CPU bound functions, and the README's framing about expensive functions is consistent with that.

Alternatives: greenlet and react-hooks-worker

The README lists two similar projects, and the difference is in how much of the worker lifecycle each one hands you.

greenlet takes a different route to the same goal. It turns a function into a web worker by stringifying it, and you call the result directly. It is not React specific, so there is no hook, no status enum, and no cleanup tied to a component lifecycle. If you are not in React, or you want to manage worker lifetime yourself, greenlet is the simpler dependency.

react-hooks-worker goes the other way. It is built around a worker you supply, typically created through a bundler's worker import, and the hook exposes a state stream of pending, complete and error results. That means you write and maintain a worker file, but you also get a result stream rather than a single promise, and you avoid the function-stringify approach that causes the Babel reference errors described in useWorker's known issues.

So the split is: useWorker trades worker-file plumbing for a stringified function and the constraints that follow. react-hooks-worker trades a worker file for a cleaner execution model. greenlet trades React integration for a smaller, framework agnostic primitive. The README also points at worker-loader for anyone ready to change the webpack configuration directly, which is the option useWorker was built to avoid.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-06-06. The most recent release listed is @koale/[email protected] on 2025-03-09, preceded by 4.1.1 on 2025-03-01 and 4.1.0 on 2024-10-28. The gaps between those releases are months apart, so plan upgrades around that rhythm rather than expecting continuous churn.

Versioning is handled with changesets, and the root package.json wires ci:version, ci:build and ci:publish scripts around it. For an application team the practical effect is that releases are cut deliberately, and a version bump is a deliberate act rather than a side effect of merging. The root package.json also enforces pnpm through a preinstall script that runs only-allow pnpm, which matters if you plan to build the monorepo locally rather than just consume the published package.

The licence is MIT, which permits commercial use and modification, and the README links the LICENSE file at the repository root. That is the extent of what can be said here; read the licence text and your own legal advice for anything beyond that.

The upgrade cost is tied to the stringify model. A minor release can change how functions are serialized or how statuses are reported, and because your worker functions are ordinary functions in your codebase, a change in that path shows up as a runtime reference error rather than a compile error. Pinning the version and reading the changelog before bumping is cheaper than discovering it in production.

Editorial conclusion

Adopt useWorker if you have a Create React App project with a genuinely expensive pure function and no ability to change the bundler, and you have read the limitations page before writing the function. Do not adopt it if your work is I/O bound, needs a pool of workers, or calls closure variables, because the hook moves the function body to a worker and those references will not survive. Before committing, verify that your function is self-contained, check whether Babel transpilation breaks it, and confirm the timeout and remote dependency options behave as the API docs describe.

Frequently asked questions

What is the main difference between Web Workers and WebAssembly?

The README does not compare the two, so this cannot be answered from the project's own material. useWorker only concerns web workers, which run JavaScript on a background thread; WebAssembly is a separate compilation target and is not mentioned anywhere in the repository files.

What are web workers used for?

In useWorker's framing, they are used to run expensive functions without blocking the UI. The README recommends reading the MDN web worker documentation before using the hook, and its own examples run sorting and CSV parsing off the main thread.

What is a js worker?

The README treats a worker as a background execution context that the hook creates and disposes of for you. The library's stated approach is to move the entire function passed to the hook into a worker, so the function runs outside the React render thread.

What are web workers in Angular and what are their uses?

This question is about Angular, and useWorker is a React hook, so the project's material does not cover it. The README only discusses React, Create React App and the React hook API.

Official sources

  1. alewin/useWorker on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alewin-useworker.svg)](https://hysenlabs.com/projects/alewin-useworker)