1

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 :)

Lorenz Weiß
  • 279
  • 3
  • 10
  • 1
    Possible duplicate of [How to cancel a fetch on componentWillUnmount](https://stackoverflow.com/questions/49906437/how-to-cancel-a-fetch-on-componentwillunmount) – r g Mar 12 '19 at 13:43
  • @fard no this about react hooks and not the normal react lifecycle methods – Lorenz Weiß Mar 12 '19 at 13:45
  • 1
    there is no real difference and even answers have been written in hooks – r g Mar 12 '19 at 13:46
  • Could you explicit why cancelling the request won't work for you ? Are you using the data somewhere else ? – Bear-Foot Mar 12 '19 at 15:04
  • 1
    `so with hooks you don't need to cancel the asynchronous callback that's what it does automatically.` It won't be done automatically, we've gotta take care of cleanup ourselves. (removing event listeners, cancelling network requests, etc.) – Pa Ye Mar 12 '19 at 16:26

3 Answers3

2

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>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 1
    mh interesting approach. I was using a similar approach for my class components and this would be the equivalent. I somehow thought this could be solved easier with hooks. – Lorenz Weiß Mar 12 '19 at 13:17
  • 1
    @Tholle Making cancellable promises is easier, clean solution provided by React guys and it works perfectly with hooks https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html https://github.com/facebook/react/issues/5465#issuecomment-157888325 – r g Mar 12 '19 at 13:59
  • 1
    @fard The `isMounted()` method is an antipattern, yes, but keeping a ref is fine. Introducing a cancellable promise abstraction is overkill is many situations. – Tholle Mar 12 '19 at 14:01
  • @Tholle im talking about the last section >Ideally, any callbacks should be canceled, prior to unmounting. – r g Mar 12 '19 at 14:02
0

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.

Liam Ross
  • 23
  • 1
  • 3
-1

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.

Pa Ye
  • 1,779
  • 12
  • 22