Best Practices for using Web Workers with React Hooks and Lifecycle Events
Recently I ran into some issues with the way our team was managing web workers in our React project. We use the workers to offload some heavy searching using the fuse.js library with large datasets. I was surprised to find that there wasn’t a lot written about this already (although maybe my google-fu was lacking).
Our original setup looked something like this:
By doing it this way, the worker is instantiated at a higher level scope than the component itself, which causes some problems. First, the worker is always created at runtime even before the component is ever mounted to the DOM, (even if the component is NEVER mounted to the DOM), so it just sits there uselessly taking up memory. Second, if you tried to create two instances of SomeComponent
, the second instance will overwrite the searchWorker variable and make it inaccessible in the first instance of the component.
My first attempt to fix this and move it into the lifecycle of the component was by utilizing useState
like so:
Of course, those who are more familiar with React than myself will notice a problem with this instantly: the SearchWorker
is being instantiated every time the component re-renders, which can cause dozens of instances to fill up the memory depending on how often this happens.
So we need a way to create and destroy the workers in conjunction with the lifecycle of the React component. Let’s move the instantiation of the worker inside the useEffect hook for component initialization:
We’re getting closer, but we still have a couple problems. We need to be mindful of the react lifecycle and remember that onmessage
andpostMessage
will not work at this point as the seachWorker
variable we’re setting in state won’t become available until the next cycle. We also want to explicitly terminate the worker instance when the component unmounts. We can fix both of those problems by using a temporary searchWorkerInstance
variable scoped only to the useEffect function:
This works, but there are still a couple things to note to that I had to change for my specific use case. If the postMessage
call references some other component props or needs to also be called in other methods of the component, then we need to move the postMessage
call into it’s own hook. Again this will vary slightly depending on your specific use case, but my final code ended up looking something like this:
Pulling out the postMessage
call into it’s own useEffect
hook allows us to reference any props we might need to react to and send to the web worker. The reason we must wrap the postMessage
in an if
is that this hook will run the first time the component mounts, at which point searchWorker
will be undefined. Then, after the first useEffect
runs and sets the searchWorker
, the second useEffect
hook will run again and will now be able to post messages to the worker successfully.
You can verify that the workers are being created and destroyed with the lifecycle of your component by watching the Memory tab in Chrome dev tools (Javascript VM instances).
Hope this helps someone with integrating workers into their React project properly. If I missed anything important please let me know!