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

5
  • 1
    Possible duplicate of How to cancel a fetch on componentWillUnmount Commented Mar 12, 2019 at 13:43
  • @fard no this about react hooks and not the normal react lifecycle methods Commented Mar 12, 2019 at 13:45
  • 1
    there is no real difference and even answers have been written in hooks Commented Mar 12, 2019 at 13:46
  • Could you explicit why cancelling the request won't work for you ? Are you using the data somewhere else ? Commented Mar 12, 2019 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.) Commented Mar 12, 2019 at 16:26

3 Answers 3

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>

Sign up to request clarification or add additional context in comments.

4 Comments

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.
@Tholle Making cancellable promises is easier, clean solution provided by React guys and it works perfectly with hooks reactjs.org/blog/2015/12/16/ismounted-antipattern.html github.com/facebook/react/issues/5465#issuecomment-157888325
@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 im talking about the last section >Ideally, any callbacks should be canceled, prior to unmounting.
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.

Comments

-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.

2 Comments

The timeout was only for mocking a api request time. But thanks
Yeah that’s clear, I just wanted to demonstrate how to arrange for a cleanup in this case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.