Avoid Async Memory Leaks With This useSafeDispatch Hook

I found out recently that one great way to create a memory leak in React is by trying to manage state on a component that has been unmounted.

What if we could do this without having to much with our http requests? I found this great little hook from the legendary Kent C. Dodds to wrap your dispatch function and do just that.

function useSafeDispatch(dispatch) {
  const mounted = React.useRef(false)

  React.useLayoutEffect(() => {
    mounted.current = true
    return () => (mounted.current = false)
  }, [])

  return React.useCallback(
    (...args) => (mounted.current ? dispatch(...args) : void 0),
    [dispatch],
  )
}

It's simple enough, we start by creating a ref and initialize it to false.

Then, using React.useLayoutEffect with an empty dependency array, we set that ref to true when the component mounts (but before it renders - read more here for the differences between useEffect and useLayoutEffect).

The return function on useEffect and useLayoutEffect (with an empty dependency array) will run when the component unmounts, and the ref will be set back to false.

Putting it all together:

Now we have a ref that tells us in real time whether the component is mounted or not, and our hook receives the dispatch function as an argument. We just need to give them back a new version of the dispatch function that doesn't run if the component is unmounted (we don't care if the fetch finishes, as long as we don't try to setState or dispatch).

return React.useCallback(
    (...args) => (mounted.current ? dispatch(...args) : void 0),
    [dispatch],
  )

Here we spread the args from the dispatch function into our new dispatch function that returns void if our ref indicates that the component isn't mounted. Finally, we wrap it all in a useCallback so that people using our code can use it as a dependency.