Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useCallback memory leak unmounted component

I'm new to React and I'm getting this error:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

From what I understand, it appears as though I have a memory leak and need a useEffect cleanup for my useCallback hook?

I have tried adding a useRef to check mount but the return doesn't change the state to false.


const MyComponent = ({data}) => {
  const mounted = useRef(true);
  const [loading, setLoading] = useState(false);

  const isLoading = useCallback(async () => {
    setLoading(true);

    if (data) {
      console.log('YAY DATA: ', data);
    }

    return () => {
      setLoading(false); // unreachable code
      mounted.current = false; // does this do the cleanup?
    };
  }, [loading]);

  return (
    //...some component
  );
};

export default MyComponent;

like image 546
JaeLeeSo Avatar asked Mar 09 '21 21:03

JaeLeeSo


1 Answers

If you perform an async operation that, on completion, needs to set something to the state, you must make sure that the component is still mounted before updating the state.

In order to do so, you try something like this:

const MyComponent = (props) => {
  const mounted = useRef(false);

  useEffect(() => {
        mounted.current = true; // Will set it to true on mount ...
        return () => { mounted.current = false; }; // ... and to false on unmount
  }, []);

  const [loading, setLoading] = useState(false);
  const [data, setData] = useState([]);

  const myAsyncCallback = useCallback(async () => {
    setLoading(true);

    // Await for an async function...
    const data = await yourAsyncFunction(...); // Complete with proper code here

    // While waiting for yourAsyncFunction to be done,
    // It's possible that your component had been unmounted.

    // Therefore, you have to check if the component is still mounted before updating states
    if (mounted.current) { 
      setLoading(false);
      setData(data);
    }
  }, []);

  return (
    //...some component that calls myAsyncCallback
  );
};

export default MyComponent;
like image 163
dglozano Avatar answered Nov 21 '22 04:11

dglozano