Snippet

useAsyncSafeState()

edit

If you ever find yourself calling a state setter in an async process, there’s a reasonable chance that you may call it after the component unmounts and therefore incur an error indicating a memory leak in you application.

The way to solve for this is to prevent the setState call if the component has unmounted. We can track the isMounted state of a hook using a ref, and wrap the state setter to automatically check this value before making its update. Like so:

function useAsyncSafeState(value) {
  // Track the mounted state of the component using the hook
  const isMounted = React.useRef(true)

  // If the component unmounts, update the `isMounted` state
  React.useEffect(() => {
    return () => {
      isMounted.current = false
    }
  }, [])

  const [state, setState] = React.useState(value)

  // Prevent `setState` calls after the component unmounts
  const safeSetState = React.useCallback(update => {
    if (isMounted.current) {
      setState(update)
    }
  }, [])

  return [state, safeSetState]
}

Now you can make use of this in a situation where there’s a chance that component unmounts before the state is set. Like fetching data:

function MyComp() {
  const [data, setData] = useAsyncSafeState(null)

  React.useEffect(() => {
    function getData() {
      fetch('/path/to/data')
        .then(result => {
          setData(result)
        })
        .catch(console.error)
    }

    getData()
  }, [setData]) // linter will not know that this is stable.

  return (
    data && (
      <pre>
        <code>{JSON.stringify(data, null, 2)}</code>
      </pre>
    )
  )
}

If the user leaves the page with MyComp or its unmounted for some other reason, setData won’t create an error.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.

Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Logo for Array.reduce()
Array.reduce()
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.