DEV Community

[Comment from a deleted post]
Collapse
 
dikamilo profile image
dikamilo

I will add here: No not update state on component unmount. Very common mistake that also is in one of your examples:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  // Directly update state
  const update = () => setCount(count + 1);

  // Directly update state after 3 sec
  const asyncUpdate = () => {
    setTimeout(() => {
      setCount((currentCount) => currentCount + 1);
    }, 2000);
  };

  // Render UI
  return (
    <div className='App'>
      <span>Count: {count}</span>
      <button onClick={update}>Add +1</button>
      <button onClick={asyncUpdate}>Add +1 later</button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

asyncUpdate method create timer that after 3 seconds, updates component state. After 3 seconds, the component may be unmounted (and timer still running in bacground since you didn't clean it), and we have a memory leak.

Two notes here:

  • always clear timeouts/intervals
  • check if component is mounted in your async code

const mounted = useRef(false);
let timeoutHandler= null;

useEffect(() => {
    mounted.current = true;

    return () => {
        mounted.current = false;

        // clear timeout on component unmount
        if(timeoutHandler) {
            clearTimeout(timeoutHandler)
        }
    };
}, []);

const asyncUpdate = () => {
    // still caveat here, multiple calls to asyncUpdate will overide timeoutHandler so we should clear it here befeore create new one
    if(timeoutHandler) {
        clearTimeout(timeoutHandler)
    }

    timeoutHandler = setTimeout(() => {
        // do not update component state when is not mounted
        if(mounted.current) {
            setCount((currentCount) => currentCount + 1);
        }
    }, 2000);
  };

Enter fullscreen mode Exit fullscreen mode