How to cancel asynchronous tasks in a useEffect cleanup function? - reactjs

I am getting this error:
index.js:1 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.
index.js:1 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.
at Products (http://localhost:3000/static/js/main.chunk.js:2779:5)
at div
at Home
at RenderedRoute (http://localhost:3000/static/js/vendors~main.chunk.js:246119:5)
at Routes (http://localhost:3000/static/js/vendors~main.chunk.js:246568:5)
at Router (http://localhost:3000/static/js/vendors~main.chunk.js:246499:15)
at BrowserRouter (http://localhost:3000/static/js/vendors~main.chunk.js:244709:5)
at div
at App
I assume the problem is here:
Products.js
const [products, setProducts] = useState([]);
useEffect(() => {
const getProdcuts = async () => {
try {
const res = await axios.get(
category
? `http://localhost:5000/e-mart/products?category=${category}`
: `http://localhost:5000/e-mart/products`
);
setProducts(res.data);
} catch (err) {
console.log(err.message);
}
};
getProdcuts();
}, [category]);
My home page is not loading. No problem is shown in the terminal. How can I resolve this?

You can use the AbortController to abort an async request, it'd look like
useEffect(() => {
const abortController = new AbortController();
const getProdcuts = async () => {
try {
const res = await axios.get(
category
? `http://localhost:5000/e-mart/products?category=${category}`
: `http://localhost:5000/e-mart/products`,
{ signal: abortController .signal } // Notice this line here
);
setProducts(res.data);
} catch (err) {
console.log(err.message);
}
};
getProdcuts();
return () => {
// Function returned from useEffect is called on unmount
// Here it'll abort the fetch
abortController.abort();
}
}, [category]);
That's a quick way to fix this problem but if you're not sure why this problem is happening in the first place it's probably because you're not expecting Products to unmount.
You might have a parent component rendering Products or one of its parents conditionnally and a state change cause an unmount before fetch is done.

Related

Trying to implement a cleanup in a useEffect to prevent no-op memory leak error

I am trying to update a piece of UI based on a conditional. The conditional is set by a database call in a separate component. It sometimes works, but often doesn't. When it doesn't work, it gets this 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.
I have followed other advice and tried to do a cleanup in a useEffect:
const [isPatched, setIsPatched] = useState<boolean>(false);
useEffect(() => {
x.Log ? setPatched(true) : setPatched(false);
return () => {
setIsPatched(false);
};
}, []);
const setPatched = (patched: boolean) => {
setIsPatched(patched);
};
Other component db call:
useEffect(() => {
if (disabled === true) {
const handle = setTimeout(() => setDisabled(false), 7000);
return () => clearTimeout(handle);
}
}, [disabled]);
function handleClick() {
[...]
const updatePatchedX = async (id: string) => {
//check if patched x already in db
const content = await services.xData.getxContent(id);
const xyToUpdated = content?.p[0] as T;
if (!xToUpdated.log) {
// add log property to indicate it is patched and put in DB
xToUpdated.log = [
{ cId: cId ?? "", uId: uId, appliedAt: Date.now() },
];
if (content) {
await services.xData
.updateOTxBook(id, content, uId)
.then(() => {
console.log("done");
setPatched(true);
setDisabled(true);
});
}
}
};
updatePatchedX(notebookID);
}
The UI is only fixed on refresh - not immediately, as the useEffect is supposed to achieve? Not sure where to go from here. Could be a race condition?
I have experienced this in the past and here's what I learned from it
This is normally caused by this sequence of events:
user clicks button => triggers API call => UI changes and the button gets unmounted => API call finishes and tries to update the state of a component that has been unmounted
If the action could be canceled then the default recommendation of "To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." makes sense; but in this case the API call cannot be cancelled.
The only solution I've found to this is using a ref to track whether the component has been unmounted by the time the API call completes
Below a snippet with just the relevant changes for simplicity
const mainRef = useRef(null);
function handleClick() {
[...]
const updatePatchedX = async (id: string) => {
...
await services.xData
.updateOTxBook(id, content, uId)
.then(() => {
if (mainRef.current) {
console.log("done");
setPatched(true);
setDisabled(true);
}
});
...
};
updatePatchedX(notebookID);
}
return (
<div ref={mainRef}>.... <button onClick={handleClick}>...</button></div>
);
The above works because when the component gets unmounted the myRef references get emptied but you can still check its value when the API call eventually fulfills and before you use some setState function

Expo React 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

I'm new to react native and was following a tutorial on medium about how to connect with firebase auth. Everything seems to work fine, but I keep getting this warning below:
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.
I pretty much did exactly what was said in the tutorial and tried out a few other things to fix it, but nothing seems to work. Here is the code it's pointing the error to:
let currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
useEffect(() => {
getUserInfo();
})
async function getUserInfo(){
try {
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
}
} catch (err){
Alert.alert('There is an error.', err.message)
}
}
It would be great if anyone could help me fix this problem and explain what exactly has gone wrong.
This is the link to the tutorial I was following:
The issue here is that you are potentially enqueueing a state update after a component has unmounted. Since you are accessing your firestore directly, asynchronously, you can use a React ref to track if the component is still mounted before enqueuing the update.
const isMountedRef = React.ref(null);
useEffect(() => {
isMountedRef.current = true; // set true when mounted
return () => isMountedRef.current = false; // clear when unmounted
}, []);
useEffect(() => {
async function getUserInfo(){
try {
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
if (isMountedRef.current) { // <-- check if still mounted
setFirstName(dataObj.firstName);
}
}
} catch (err){
Alert.alert('There is an error.', err.message)
}
}
getUserInfo();
}, []); // <-- include dependency array, empty to run once when mounting
Your getInfoUser() function is async. You should do something like this in useEffect:
useEffect(async () => { await getUserInfo(); }, [])
As a second argument in useEffect use the dependency array. Using the dependency array is equivalent with componentDidMount.
Your effect will take place only once when the component is first rendered. Additionally there are cases when you need to provide a cleanup function in useEffect something like this:
return () => {
//do something or cancel a subscription
}

Can't perform a React state update on an unmounted component warning in Reactjs

I'm getting this below warning in console when i run he code.
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.
Here is my code:
const [userDetail, setUserDetail] = React.useState('');
const personalDetails = (user_id) => {
axios
.get(`http://localhost:3001/users/${user_id}`, { withCredentials: true })
.then((response) => {
const personalDetails = response.data;
setUserDetail(response.data);
})
.catch((error) => {
console.log(" error", error);
});
};
React.useEffect(() => {
if (user_id) {
personalDetails(user_id);
}
}, [user_id]);
If I remove useEffect call, this error disappears. What is wrong here?
This seems to be what's happening:
Your component mounts.
Your effect runs, which begins making an HTTP request.
The component unmounts for whatever reason (the parent component is removing it).
The HTTP request completes, and your callback calls setUserDetail, even though the component has been unmounted.
Another issue is that you're defining the personalDetails outside of the effect where it's used.
I would make two changes:
Move the body of the personalDetails function inside the effect.
If the component unmounts, or user_id is changed, then you don't want to call setUserDetail anymore, so use an aborted variable to keep track of this.
const [userDetail, setUserDetail] = React.useState("");
React.useEffect(() => {
if (user_id) {
let aborted = false;
axios
.get(`http://localhost:3001/users/${user_id}`, { withCredentials: true })
.then((response) => {
const personalDetails = response.data;
// Don't change state if the component has unmounted or
// if this effect is outdated
if (!aborted) {
setUserDetail(response.data);
}
})
.catch((error) => {
console.log(" error", error);
});
// The function returned here is called if the component unmounts
// or if the dependency list changes (user_id changes).
return () => (aborted = true);
}
}, [user_id]);

How to fix the unmounted component in react hooks

How to fix the 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.
here' the code:
useEffect(() => {
let ignore = false;
setTimeout(() => {
const fetchData = async () => {
try {
setLoading(true);
setError({});
const response = await getLatest();
if (!ignore) setData(response['data']);
} catch (err) {
setError(err);
}
setLoading(false);
};
fetchData();
});
return () => {
ignore = true;
};
}, []);
The problem here is when I click the home page while loading the data and click the room page. then the error will appeared. How to fix on it?
You need to clean up your timeout on unmount, otherwise, it tries to execute your hooks to update the internal state of the component after it goes out of scope (I assume functions like setLoading and setError are from a React hook).
To do so, put the output of setTimeout in a variable and then call clearTimeout in the function you return from useEffect. That's the clean up function and it's run when the component gets unmounted. So you need something like this:
React.useEffect(() => {
...
const timeout = setTimeout(...)
return () => {
...
clearTimeout(timeout);
}
}, [])
See the docs for more info: https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect

Can't perform a React state update on an unmounted component in react

I am having an issue when I try to get data api from my mongoose
Here is my code:
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
getProductsAPI();
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
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.
The problem is the network request is resolved after the component unmounts. You can probably try some solution from this thread.

Resources