Shotiko Topchishvili
Shotiko Topchishvili

Reputation: 2943

How to get rid of memory leak in react web app

I have this code in my ReactJS web application:

useEffect(() => {
    const fetchInfo = async () => {
      const res = await fetch(`${api}&page=${page}`);
      setLoading(true);
      try {
        const x = await res.json();
        if (page === 1) {
          setItems(x);
          setAutoplay(true);
        } else {
          setItems({
            hasMore: x.hasMore,
            vacancies: [...items.vacancies, ...x.vacancies],
          });
        }
      } catch (err){
        console.log(err);
      }
      setLoading(false);
    };
    fetchInfo();
  }, [page]);

When this component unmounts while running asynchronous function, it throws an error in console.

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.

How can i cancel asynchronous tasks in cleanup.

Upvotes: 1

Views: 112

Answers (1)

ant_1_b
ant_1_b

Reputation: 61

I'm assuming here that setLoading is the function setting the state after your component has un-mounted, and therefore throwing this warning. If yes, then what you need is a clean-up function.

The function passed to useEffect can return a function, which will be called before the component unmounts (you can think of it as the equivalent of the old componentWillUnmount) - more details here:

https://reactjs.org/docs/hooks-effect.html#example-using-hooks

Now what you probably want is some sort of flag to check whether it is safe for you to call setLoading, i.e, set that flag to be true by default, then set it to false in the return function. Here's a good article that should help:

https://juliangaramendy.dev/use-promise-subscription/

Now I haven't tested this but essentially your code would look something like this:

useEffect(() => {
    const fetchInfo = async () => {
        let isSubscribed = true;
        const res = await fetch(`${api}&page=${page}`);
        if (isSubscribed) setLoading(true);
        try {
            const x = await res.json();
            if (page === 1) {
                setItems(x);
                setAutoplay(true);
            } else {
                setItems({
                    hasMore: x.hasMore,
                    vacancies: [...items.vacancies, ...x.vacancies]
                });
            }
        } catch (err) {
            console.log(err);
        }
        if (isSubscribed) setLoading(false);

        return () => (isSubscribed = false);
    };
    fetchInfo();
}, [page]);

Upvotes: 1

Related Questions