Reputation: 319
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
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
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
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