cleanup with React useEffect - javascript

Okay, so I generally understand the error I'm getting.
"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."
I know I need to do something along the lines of adding a mounted component in the useEffect functions. I actually am not too sure how to implement it, exactly, with what I'm doing.
useEffect(() => {
fetchShippingCountries(checkoutToken.id)
}, []);
useEffect(() => {
if (shippingCountry) fetchSubdivisions(shippingCountry);
}, [shippingCountry]);
useEffect(() => {
if (shippingSubdivision) fetchShippingOptions(checkoutToken.id, shippingCountry, shippingSubdivision);
}, [shippingSubdivision]);
I have done some research, and this appears to be the form I need to follow, but it's not working for me.
useEffect(() => {
let mounted = true;
fetchItems().then((newItems) => {
if (mounted) {
setItems(newItems);
}
});
return () => {
mounted = false;
};
}, []);

Related

How to fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function error

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.
useEffect(() => {
const unsubscribe = streamCourses({
next: (querySnapshot) => {
const task = querySnapshot.docs.map((docSnapshot) =>
mapDocTask(docSnapshot)
);
setCourseDetails(task);
},
error: (error) => console.log(error),
});
return unsubscribe;
}, [setCourseDetails]);
I had a similar issue to this. What I had to do to solve it was two things:
(1) I created a State boolean isMounted which was set to true by default and was used to wrap the contents of my useEffects so that the contents of my useEffects would only run if the screen was mounted.
(2) I created a useEffect dedicated solely to cleanup. Meaning this useEffect had nothing besides a return statement in it which set the various State variables I had to their default values.
Example:
useEffect(() => {
if (isMounted) {
const unsubscribe = streamCourses({
next: (querySnapshot) => {
const task = querySnapshot.docs.map((docSnapshot) =>
mapDocTask(docSnapshot)
);
setCourseDetails(task);
},
error: (error) => console.log(error),
});
return unsubscribe;
}
}, [setCourseDetails]);
useEffect(() => {
return () => {
setCourseDetails(null);
setIsMounted(false);
}
}, []);

Can't perform a React state update on an unmounted component. Help Me Please

I am creating a user session asking the server through an api if the session already exists and if it exists change the state to the session to true but I get this error "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. "
const [session, setSession] = useState();
Axios.defaults.withCredentials = true;
useEffect(() => {
Axios.get('http://localhost:3001/admin/api/session').then((res) => {
if (res.data.login === true){
setSession(true);
}
})
}, []);
if(session){
return <Route {...restoDePropiedades}>{children}</Route>
}else{
return <Redirect to="/admin-login"/>
}
The problem is that your component has unmounted before the async Axios request completes. Therefore, it's trying to call setSession after the component has unmounted.
You can use a ref to store the mounted state of the component and use it in an if condition when the Axios request completes:
const unmounted = useRef(false);
useEffect(() => {
Axios.get('http://localhost:3001/admin/api/session').then((res) => {
// Check if component has unmounted before trying to update state
if (!unmounted.current) {
if (res.data.login === true) {
setSession(true);
}
}
})
}, []);
useEffect(() => {
// This function will run when the component unmounts
return () => {
unmounted.current = true;
}
}, []);
You could follow the approach that each component that makes an async request should handle at least the most common states, e.g. loading and error, so you could introduce a loading state at the very least, especially if you need to check the session against the API before doing anything
e.g.
const [session, setSession] = useState();
const [loading, setLoading] = useState(true)
useEffect(() => {
Axios.get('http://localhost:3001/admin/api/session').then((res) => {
if (res.data.login === true){
setSession(true);
}
})
.finally(() => setLoading(false))
}, []);
if (loading) return <Loading />
if(session){
return <Route {...restoDePropiedades}>{children}</Route>
}else{
return <Redirect to="/admin-login"/>
}
or whatever way you need to handle the routes when its loading

Having problem with my React Native App: Can't perform a React state update on an unmounted component

I am new to React and I am getting this error. Can anyone help me on how to sort this. Thanks!
Full Warning Statement:
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.
Sample code:
useEffect(() => {
fetchBlogs();
const subscription = DataStore.observe(Blog).subscribe(() => fetchBlogs());
return () =>{
subscription.unsubscribe();
};
}, [])
async function fetchBlogs() {
const blogData = await DataStore.query(Blog);
const sortedBlogs = blogData.sort((a,b) => b.createdAt > a.createdAt);
if (findTrueItem(sortedBlogs) !== undefined)
{
arrayMove(sortedBlogs,findPosition(sortedBlogs), 0);
}
setBlogs(sortedBlogs);
}

Returning a callback from useEffect

Sorry for the newbie question:
I'm using useEffect to avoid setting state on an unmounted component, and I was wondering why does this work:
useEffect(() => {
let isMounted = true
actions.getCourseDetails(fullUrl)
.then(data => {
if (isMounted) {
actions.setOwner(data.course.Student.id);
setDetails(data.course);
}
});
return () => {
isMounted = false;
}
}, [actions, fullUrl]);
...but when I return a variable instead of a callback it doesn't work?:
useEffect(() => {
let isMounted = true
actions.getCourseDetails(fullUrl)
.then(data => {
if (isMounted) {
actions.setOwner(data.course.Student.id);
setDetails(data.course);
}
});
isMounted = false;
return isMounted; //returning a variable instead of a callback
}, [actions, fullUrl]);
Thanks!
The syntax of useEffect is to optionally return a dispose function. React will call this dispose function ONLY when one of the dependencies changes or when it unmounts. to "release" stuff that no longer relevant.
For example, you want to wait X seconds after the render, and then change the state:
useEffect(() => {
setTimeout(() => setState('Timeout!', timeToWait));
}, [timeToWait])
Imagen that this component mounts and then after one second unmounts. Without a dispose function the timer will run and React will try to run setState on unmounted component, this will result in an error.
The proper way to do it is to use the dispose function:
useEffect(() => {
const id = setTimeout(() => setState('Timeout!', timeToWait));
return () => clearTimeout(id);
}, [timeToWait])
So every time the timeToWait dependency changes for some reason, the dispose function will stop the timer and the next render will create a new one with the new value. or when the component unmounts.
In your example, the order of execution will be:
Define isMounted and set it to true
Start async action (this will run next tick)
Set isMounted to false
return a variable (Not a function)
So you have 2 problems in your (Not-working) example. you don't return a dispose function, and you change isMounted to false almost immediately after you define it. when the promise will run the isMounted will be false no matter what. If you'd use a dispose function (The working example), only when React will call it the isMounted to turn to false

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

Categories