Web workers are worker threads that run tasks and other functionalities in the background.
In this guide, we’ll explore the basics of using web workers, walk through useWorker
implementation, and build a demo app so you can see the library in action. We’ll also go over some real-world use cases and interesting facts about web workers.
Why use web workers?
You might be wondering, “Why would I need to run anything in the background?” Take a look at the demo below.
As you can see, when you click the button, the app’s UI freezes for a few seconds. After a pause, a toast shows that the action is completed.
Here we have two components: one to render the animation and another to handle the heavy computation of thousands of records. As soon as you click the button, it starts the computation, which leads to the UI freezing.
Obviously, this makes for a subpar user experience. The user shouldn’t have to wait for a specific action to be completed. Web workers solve this problem by enabling you to run high-volume computational tasks in the background without affecting the UI thread.
How web workers work
When your application needs to run a heavy computational task, a worker thread will handle the computation in a thread separate from the main UI thread. You can pass the data to the worker threads from the main thread.
Once a worker thread completes a task, it will post a message to the main thread. You can then update your main UI thread based on this message.
Implementation
To paint a basic picture and demonstrate web workers’ ease of use, let’s start by implementing a web worker without using useWorker
hooks.
Web worker API
Create a react component and add the following code.
import React, { useEffect, useState } from "react"; import { fetchUsers } from "../../utils/sortData"; import { useToasts } from "react-toast-notifications"; import "./index.css"; import webworker from "../../utils/app.worker"; import WorkerClass from "./Worker"; const WebWorker = () => { const [workerState, setWorkerState] = useState("DESC"); const { addToast } = useToasts(); const sortWorkerData = async () => {}; return ( <div className="user-div"> <button className="sort-button" onClick={sortWorkerData} > {" "} Worker Sort </button> </div> ); }; export default WebWorker;
Here, we have a button that, when clicked, performs a heavy computational task. Lets bring this functionality to our web worker.
First, create a wrapper class around the Worker
class in JavaScript, Worker.js
.
export default class WebWorker { constructor(worker) { const code = worker.toString(); const blob = new Blob(["(" + code + ")()"]); return new Worker(URL.createObjectURL(blob)); } }
This returns a new Worker
object with the worker thread function.
Next, import the Worker
class wrapper in your component.
import WorkerClass from "./Worker";
The next step is to create the worker thread to handle the computational task.
export default () => { self.addEventListener("message", (e) => {// eslint-disable-line no-restricted-globals if (!e) return; let users = e.data; const copy = [...users]; for (var i = 0; i < copy.length - 1; i++) { for (var j = i + 1; j < copy.length; j++) { if (copy[i].commentCount < copy[j].commentCount) { var t = copy[i]; copy[i] = copy[j]; copy[j] = t; } } } postMessage(copy); }); };
The above code adds an event listener where the thread performs a computational task and postMessage
after it’s done.
Let’s import that in our component.
import webworker from "../../utils/app.worker";
Now it’s time to wrap the component. Add the function.
const sortWorkerData = async () => { try { const users = await fetchUsers(); let worker = new WorkerClass(webworker); worker.postMessage(users); worker.addEventListener("message", function (event) { setWorkerState("ASC"); // setUsers(sortedData) addToast("Finished: Sort using useWorker.", { appearance: "success" }); }); } catch (e) { console.log(e); addToast("Some Error Occurred", { appearance: "error" }); } };
First, fetch all the user data and create a worker object before instantiating the WorkerClass
, which is a wrapper of Worker
.
Next, postMessage
to the worker thread with the user’s data. Once the computational task is done, it fires the event listened with the result.
useWorker
So far, we’ve gone over how to implement the web worker API in a React application. One problem with the web worker API is that it’s difficult to track the status of a worker.
As you can see in the demo, it’s hard to tell whether a worker thread is running unless it is completed. This will become increasingly difficult as the number of threads in your application increases.
useWorker
adds four main features to the plate:
- Tracking worker threads status
- Option to kill the thread
- Maintainability
- Readability
To install useWorker
in your application:
npm i @koale/useworker
Let’s implement useWorker
to see it in action.
import React, { useEffect, useState } from "react"; import { fetchUsers, sortListDescending, sortListAscending, } from "../../utils/sortData"; import { useWorker, WORKER_STATUS } from "@koale/useworker"; import { useToasts } from "react-toast-notifications"; import "./index.css"; const UserList = () => { const [users, setUsers] = useState([]); const { addToast } = useToasts(); const [ sortListDescWorker, { status: DescWorkerStatus, kill: DescWorkerKill }, ] = useWorker(sortListDescending); useEffect(() => { async function fetchData() { const user = await fetchUsers(); setUsers(user); } fetchData(); }, []); const sortWorkerData = async () => { try { const users = await fetchUsers(); await sortListDescWorker(users); addToast("Finished: Sort using useWorker.", { appearance: "success" }); } catch (e) { console.log(e); addToast("Some Error Occurred", { appearance: "error" }); } }; return ( <div className="user-div"> <button className="sort-button" disabled={DescWorkerStatus === WORKER_STATUS.RUNNING} onClick={sortWorkerData} > {DescWorkerStatus === WORKER_STATUS.RUNNING ? `Loading` : "useWorker Sort"} </button> {DescWorkerStatus === WORKER_STATUS.RUNNING ? ( <button className="sort-button" onClick={DescWorkerKill}> Kill Worker </button> ) : null} </div> ); }; export default UserList;
First, implement useWorker
with a function that has a heavy computational task.
const [ sortListDescWorker, { status: DescWorkerStatus, kill: DescWorkerKill }, ] = useWorker(sortListDescending);
useWorker
exports three parameters:
- Worker thread invoke function
- Status of worker thread
- Function to kill the thread
Invoke the worker thread function along with the data to be passed.
const sortWorkerData = async () => { try { const users = await fetchUsers(); await sortListDescWorker(users); addToast("Finished: Sort using useWorker.", { appearance: "success" }); } catch (e) { console.log(e); addToast("Some Error Occurred", { appearance: "error" }); } };
It’s as easy as that! useWorker
updates the status of the web worker to the component. You can use it to update the UI based on its status.
useWorker
final demo
Main features of useWorker
Let’s take a step back and review the main advantages of using useWorker
.
As mentioned earlier, useWorker
provides us a good wrapper function to update the UI based on the status of a worker thread. It also provides a wrapper function to kill the thread.
useWorker
hooks enable you to write readable and maintainable code using the web worker API.
Let’s see what the code looks like with and without useWorker
.
Without useWorker
With useWorker
Conclusion
We all know that hooks are the preferred way to write components in the React ecosystem. useWorker
will be the most elegant way to use web workers in your application, mainly due to the readability and maintainability benefits.
Try it out yourself and tell us how it’s going in the comments. Happy coding!
Full visibility into production React apps
Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.