We use cookies to enhance your experience on the site
CodeWorlds

Web Workers in React

Imagine that on your spaceship, the main onboard computer (main thread) must simultaneously render the cockpit, handle navigation systems, and calculate flight trajectories. If the trajectory calculations are too heavy, the cockpit freezes -- the captain cannot click any button. The solution? A dedicated computation computer (Web Worker) that works in parallel without blocking the main system.

What Are Web Workers?

Web Workers are a browser mechanism that allows running JavaScript code in a separate thread. The main thread is responsible for UI rendering, event handling, and user interaction. When you perform heavy computations on the main thread, the interface "freezes."

1// WITHOUT Web Worker - blocks main thread
2function HeavyCalculation() {
3  const [result, setResult] = useState(null);
4
5  const calculate = () => {
6    // This will block the UI for several seconds!
7    let sum = 0;
8    for (let i = 0; i < 1_000_000_000; i++) {
9      sum += Math.sqrt(i);
10    }
11    setResult(sum);
12  };
13
14  return (
15    <div>
16      <button onClick={calculate}>Calculate</button>
17      <p>Result: {result}</p>
18    </div>
19  );
20}

Creating a Web Worker

A Web Worker is a separate JavaScript file that communicates with the main thread through a message system (

postMessage
/
onmessage
):

1// worker.js - Worker file (separate thread)
2self.onmessage = function(event) {
3  const { type, data } = event.data;
4
5  if (type === 'CALCULATE_TRAJECTORY') {
6    const result = calculateTrajectory(data);
7    self.postMessage({ type: 'RESULT', result });
8  }
9};
10
11function calculateTrajectory(params) {
12  let trajectory = [];
13  for (let t = 0; t < params.steps; t++) {
14    const x = params.speed * t * Math.cos(params.angle);
15    const y = params.speed * t * Math.sin(params.angle) - 0.5 * 9.81 * t * t;
16    trajectory.push({ x, y, t });
17  }
18  return trajectory;
19}
1// App.jsx - React component (main thread)
2import { useState, useRef, useEffect } from 'react';
3
4function TrajectoryCalculator() {
5  const [result, setResult] = useState(null);
6  const [isCalculating, setIsCalculating] = useState(false);
7  const workerRef = useRef(null);
8
9  useEffect(() => {
10    workerRef.current = new Worker(new URL('./worker.js', import.meta.url));
11
12    workerRef.current.onmessage = (event) => {
13      if (event.data.type === 'RESULT') {
14        setResult(event.data.result);
15        setIsCalculating(false);
16      }
17    };
18
19    return () => workerRef.current?.terminate();
20  }, []);
21
22  const handleCalculate = () => {
23    setIsCalculating(true);
24    workerRef.current.postMessage({
25      type: 'CALCULATE_TRAJECTORY',
26      data: { speed: 1000, angle: 0.785, steps: 100000 }
27    });
28  };
29
30  return (
31    <div>
32      <button onClick={handleCalculate} disabled={isCalculating}>
33        {isCalculating ? 'Calculating...' : 'Calculate trajectory'}
34      </button>
35      {result && <p>Trajectory points: {result.length}</p>}
36    </div>
37  );
38}

Custom Hook: useWorker

To avoid repeating the logic for creating and managing Workers, we can create a reusable hook:

1import { useState, useRef, useEffect, useCallback } from 'react';
2
3function useWorker(workerFactory) {
4  const [result, setResult] = useState(null);
5  const [error, setError] = useState(null);
6  const [isRunning, setIsRunning] = useState(false);
7  const workerRef = useRef(null);
8
9  useEffect(() => {
10    workerRef.current = workerFactory();
11
12    workerRef.current.onmessage = (event) => {
13      setResult(event.data);
14      setIsRunning(false);
15    };
16
17    workerRef.current.onerror = (err) => {
18      setError(err.message);
19      setIsRunning(false);
20    };
21
22    return () => workerRef.current?.terminate();
23  }, []);
24
25  const run = useCallback((data) => {
26    setIsRunning(true);
27    setError(null);
28    workerRef.current?.postMessage(data);
29  }, []);
30
31  return { result, error, isRunning, run };
32}

Comlink - Simplified Communication

The Comlink library (from Google Chrome Labs) lets you treat Web Workers like regular objects with async methods:

1// worker.js with Comlink
2import * as Comlink from 'comlink';
3
4const navigationComputer = {
5  async calculateRoute(origin, destination) {
6    const route = computeOptimalPath(origin, destination);
7    return route;
8  },
9  async analyzeStarField(stars) {
10    return stars.map(star => ({
11      ...star,
12      habitableZone: calculateHabitableZone(star.mass, star.luminosity)
13    }));
14  }
15};
16
17Comlink.expose(navigationComputer);
1// App.jsx with Comlink
2import * as Comlink from 'comlink';
3
4function NavigationPanel() {
5  const [route, setRoute] = useState(null);
6  const navComputer = useRef(null);
7
8  useEffect(() => {
9    const worker = new Worker(new URL('./worker.js', import.meta.url));
10    navComputer.current = Comlink.wrap(worker);
11    return () => worker.terminate();
12  }, []);
13
14  const planRoute = async () => {
15    // Call the method like a regular async function!
16    const result = await navComputer.current.calculateRoute(
17      { x: 0, y: 0, z: 0 },
18      { x: 4.2, y: 1.3, z: 0.7 }
19    );
20    setRoute(result);
21  };
22
23  return (
24    <div>
25      <button onClick={planRoute}>Plan route</button>
26      {route && <RouteVisualization data={route} />}
27    </div>
28  );
29}

When to Use Web Workers in React?

Web Workers are ideal for:

  • Heavy mathematical computations - simulations, data analysis
  • Parsing large data sets - CSV, JSON with thousands of records
  • Image processing - filters, compression
  • Search and filtering - fuzzy search in large collections

Web Workers are NOT needed for:

  • Simple state operations
  • API requests (fetch/axios)
  • DOM manipulation (Workers do not have access to DOM)

Summary

  1. Web Workers - separate thread for heavy computations
  2. Communication -
    postMessage
    /
    onmessage
  3. Custom hook
    useWorker
    - encapsulation of Worker logic
  4. Comlink - simplified API (async methods instead of messages)
  5. Limitations - no access to DOM, separate scope
Go to CodeWorlds