# Improve Table Speed in React by Using Web Workers for Filters

> TL;DR: I implemented a **Web Worker–powered filtering system** in a React data table component to eliminate UI lag and improve responsiveness when working with large datasets. Here's how and why.

## The Problem: Filtering Slows the UI

Our application relies heavily on a custom `<NewTable />` component that supports:

* Nested (hierarchical) rows
    
* Column-based filtering
    
* Custom filters
    
* Server-side pagination
    

As datasets grew into the thousands of rows, filtering became noticeably sluggish. The culprit? All filtering logic ran on the **main UI thread**, blocking React’s render cycle and causing the interface to freeze temporarily.

## Goal

Move heavy data-filtering logic off the main thread using **Web Workers**, without breaking existing functionality or developer experience.

## The Solution: Asynchronous Filtering with Web Workers

We built a pipeline that:

1. **Serializes hierarchical data** into a flat structure
    
2. Sends that data to a **dedicated Web Worker**
    
3. Worker filters based on column & custom filters
    
4. Sends back the result
    
5. Updates the UI reactively and shows a loader while waiting
    

## Implementation Breakdown

### 1\. Set up `search.worker.js`

We created a Web Worker dynamically using a blob:

```ts
const searchWorker = () => {
  onmessage = async (e) => {
    importScripts("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js");
    importScripts("https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js");

    // ... logic for deep filtering, date handling, and custom filters ...
    
    postMessage({ success, result });
  };
};

let code = searchWorker.toString();
code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));
const blob = new Blob([code], { type: "application/javascript" });
const workerScript = URL.createObjectURL(blob);

export default workerScript;
```

### 2\. New Utility Function

A new async utility offloads data filtering:

```ts
const asyncFilterDataWithColumnAndCustomFilters = async (
  data,
  columns,
  columnFilters,
  customFilters,
  setData,
  setIsSearching,
) => {
  const searchWorker = new window.Worker(searchWorkerScript);

  const convertedData = data.map(row => ({
    id: row.id,
    values: columns.map(col => col.selector?.(row) ?? row[col.id]),
    items: (row.items || []).map(item => ({
      id: item.id,
      values: columns.map(col => col.selector?.(item) ?? item[col.id]),
    })),
  }));

  searchWorker.postMessage({ data, convertedData, columns, columnFilters, customFilters });

  searchWorker.onmessage = (e) => {
    setData(e.data.result);
    setIsSearching(false);
    searchWorker.terminate();
  };

  searchWorker.onerror = (err) => {
    console.error("Worker error", err.message);
    setIsSearching(false);
    searchWorker.terminate();
  };
};
```

### 3\. Hook Update

We extended the existing table filter hook:

```ts
export const useFilterDataWithColumnAndCustomFilters = ({
  data,
  columns,
  columnFilters,
  customFilters,
  setIsSearching,
}) => {
  const [filteredData, setFilteredData] = useState({ data: [], diff: 0 });

  useEffect(() => {
    asyncFilterDataWithColumnAndCustomFilters(
      data,
      columns,
      columnFilters,
      customFilters,
      setIsSearching,
      setFilteredData
    );
  }, [data, columnFilters, customFilters]);

  return { filteredData: filteredData.data, diff: filteredData.diff };
};
```

### 4\. Loading Indicator

We updated the table’s loading prop:

```tsx
<NewTable
  isLoading={isLoading || isSearching}
  // ...
/>
```

## Bonus: What the Worker Can Handle

* `BOOLEAN`, `DATE`, `DATETIME`, and `NUMBER` types
    
* Interval filters (e.g. date ranges)
    
* Null checks: `eq: "null"` and `ne: "null"`
    
* Multi-select picklists
    
* Filters nested rows **and** their parents
    

## Benefits

* **UI never freezes** during filtering
    
* **Filters run faster**, even on large datasets
    
* **Code is modular** and easy to maintain
    
* **Great user experience** with real-time filtering feedback
    

## Key Takeaways

* Use **Web Workers** to offload expensive tasks in the browser.
    
* Normalize complex data structures before sending to the worker.
    
* Gracefully handle worker errors and show meaningful UI states.
    
* It's surprisingly easy to integrate with React.
    

## Final Thoughts

Offloading filtering to a Web Worker has been one of the most impactful performance wins for our frontend in recent times. If you're working with large tables or slow filters, **give workers a shot** — your users (and frame rate) will thank you.

**Want help integrating something similar into your React app?**  
Feel free to [connect with me](https://ravgeet.in/contact).

*I wrote this blog post for my company,* [*CloudAnswers*](https://cloudanswers.com/)*.*
