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:
Serializes hierarchical data into a flat structure
Sends that data to a dedicated Web Worker
Worker filters based on column & custom filters
Sends back the result
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:
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:
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:
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:
<NewTable
isLoading={isLoading || isSearching}
// ...
/>
Bonus: What the Worker Can Handle
BOOLEAN
,DATE
,DATETIME
, andNUMBER
typesInterval filters (e.g. date ranges)
Null checks:
eq: "null"
andne: "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.
I wrote this blog post for my company, CloudAnswers.
Top comments (0)