Lorenz Weiß
Lorenz Weiß

Reputation: 319

Data fetching with React hooks cleanup the async callback

I was starting to build some of my new components with the new and shiny React Hooks. But I was using a lot of async api calls in my components where I also show a loading spinner while the data is fetching. So as far as I understood the concept this should be correct:

const InsideCompontent = props => {
   const [loading, setLoading] = useState(false);

   useEffect(() => {
     ...
     fetchData()
     ...
   },[])

   function fetchData() {
     setFetching(true);
     apiCall().then(() => {
       setFetching(false)
     })
   }
}

So this is just my initial idea of how this might work. Just a small example. But what happens if the parent component has now a condition changed that this component gets unmounted before the async call is finished.

Is there somehow a check where I can check if the component is still mounted before I call the setFetching(false) in the api callback?

Or am I missing something here ?

Here is working example : https://codesandbox.io/s/1o0pm2j5yq

EDIT: There was no really issue here. You can try it out here: https://codesandbox.io/s/1o0pm2j5yq

The error was from something else, so with hooks you don't need to check if the component is mounted or not before doing a state change.

Another reason why to use it :)

Upvotes: 1

Views: 4588

Answers (3)

Liam Ross
Liam Ross

Reputation: 23

Here's a Hook for fetching data that we use internally. It also allows manipulating the data once it's fetched and will throw out data if another call is made prior to a call finishing.

https://www.npmjs.com/package/use-data-hook

(You can also just include the code if you don't want an entire package)

^ Also this converts to JavaScript by simply removing the types.

It is loosely inspired by this article, but with more capabilities, so if you don't need the data-manipulation you can always use the solution in that article.

Upvotes: 0

Pa Ye
Pa Ye

Reputation: 1838

Assuming that this is the error you've encountered:

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.

React complains and hints you at the same time. If component has to be unmounted but there is an outstanding network request, it should be cancelled. Returning a function from within useEffect is a mechanism for performing any sort of cleanup required (docs).

Building on your example with setTimeout:

const [fetching, setFetching] = useState(true);

useEffect(() => {
    const timerId = setTimeout(() => {
      setFetching(false);
    }, 4000);

    return () => clearTimeout(timerId)
  })

In case component unmounts before the callback fires, timer is cleared and setFetching won't be invoked.

Upvotes: -1

Tholle
Tholle

Reputation: 112787

You can use the useRef hook to store any mutable value you like, so you could use this to toggle a variable isMounted to false when the component is unmounted, and check if this variable is true before you try to update the state.

Example

const { useState, useRef, useEffect } = React;

function apiCall() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Foo");
    }, 2000);
  });
}

const InsideCompontent = props => {
  const [state, setState] = useState({ isLoading: true, data: null });
  const isMounted = useRef(true);

  useEffect(() => {
    apiCall().then(data => {
      if (isMounted.current) {
        setState({ isLoading: false, data });
      }
    });

    return () => {
      isMounted.current = false
    };
  }, []);
  
  if (state.isLoading) return <div>Loading...</div>
  return <div>{state.data}</div>;
};

function App() {
  const [isMounted, setIsMounted] = useState(true);

  useEffect(() => {
    setTimeout(() => {
      setIsMounted(false);
    }, 1000);
  }, []);

  return isMounted ? <InsideCompontent /> : null;
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Upvotes: 2

Related Questions