Memory leak warning in react component using hooks - reactjs

I have an issue of memory leak and I get following error in my console.
"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 use Effect cleanup function."
Here is my code:
React.useEffect(() => {
if (data) {
// I am calling my fetch method
fetch(logData)
.then((response) => response.json())
.then((data) => {
//logic
//setState called at end of logic
setData(data);
});
}
}, [value);

Maybe your component is getting unmounted due to some interaction while it is fetching the data ? like by clicking the close button (if your component has one)
to avoid running into this error you can check if your component is mounted like this
const isMounted = useRef(true);
useEffect(() => () => { isMounted.current = false }, [])
and then only setState if the component is still mounted
useEffect(() => {
fetch(res)
.then(res => res.json())
.then((data) => {
if (isMounted) {
setState(data)
}
})
}, [dep])

Related

Memory leak in react application

After clicking on submit I got this warning
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.
This is the code
const handleSubmit = async (e) => {
e.preventDefault()
let source = axios.CancelToken.source();
dispatch(login(email, password, source.token))
.then(() => {
console.log("Result from dispatch");
props.history.push("/Dashboard");//this is line which casues a warning.
window.location.reload();
})
.catch(() => {
setLoading(false);
});
}
How to avoid this warning? Any help would be appreciated.
When props.history.push('/Dashboard') is executed, your component gets unmounted, anything after you are trying to execute on this component (like a state update) would cause a memory leak.
I solved the warning by adding this variable mounded inside useEffect.
And I'm checking if a component is unmounted.
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
const handleSubmit = async (e) => {
e.preventDefault()
let source = axios.CancelToken.source();
dispatch(login(email, password, source.token))
.then(() => {
if (!mounted) {
console.log("Result from dispatch");
props.history.push("/Dashboard");
window.location.reload();
}
})
.catch(() => {
setLoading(false);
});
}

React Memory Leakage with typescript using google firestore and react router

I am currently using firestore but I am currently getting problems with memory leakage as I delete transactionally delete my component just before I leave the screen what is the best way I to avoid this leakage When I click on the link component I want to remove the notification from the database it seems to work and take me to the new page the only problem is that I get a memory leak how should I avoid this.
const loadAlerts = useCallback(() => {
const alertNotifcationsObserver = onSnapshot(notifications, (querySnapshot) => {
const alertData: any[] = []
querySnapshot.forEach((doc) => {
console.log(doc.data())
alertData.push({
...doc.data(),
doc
})
});
setAlertNotifcations(alertData)
});
return alertNotifcationsObserver
}, [notifications])
useEffect(() => {
loadAlerts();
console.log(alertNotifications)
}, []);
<IonItem onClick={async (e) => {
console.log(i.Reference)
await notificationsController(i.Reference)
}} key={i.Reference} lines='full'>
<Link
to={{
pathname: i.Link,
state: { documentReferencePath: i.Reference }
}}
>
{i.Type}
</Link>
</IonItem>
const notificationsController = async(documentReferencePath:string)=>{
try {
await runTransaction(db, async (transaction) => {
const documentReference = doc(db,documentReferencePath)
const notificationDoc = await transaction.get(documentReference);
if (!notificationDoc.exists()) {
throw "Document does not exist!";
}
transaction.delete(documentReference)
});
console.log("Notification removed successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
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.
How do I get rid of this error also I tried to add unsubscribe for loadAlerts but type script gives me a error too.
Argument of type '() => () => Unsubscribe' is not assignable to parameter of type 'EffectCallback'.
It seems unlikely you'll call the loadAlerts function outside the useEffect hook or in any other callback, move it into the useEffect hook to remove it as a dependency, and return a cleanup function from the useEffect to unsubscribe.
useEffect(() => {
const unsubscribe = onSnapshot(
notifications,
(querySnapshot) => {
const alertData: any[];
querySnapshot.forEach((doc) => {
alertData.push({
...doc.data(),
doc,
});
});
setAlertNotifcations(alertData);
});
return unsubscribe;
}, []);

"warning: can't perform a react state update on an unmounted component " But none of the methods work

This is my code, I tried bunch of methods but I don't know how to make them work properly :/
I appreciate any kind of help!
const fetchData = React.useCallback(() => {
fetch('http://5sd780beaf65.ngrok.io/api/Data')
.then(response => response.json())
.then(responseJson => {
setData(responseJson);
setFullData(responseJson.results);
setLoading(false);
})
.catch(error => {
console.error(error);
setLoading(false);
setError(err);
});
}, []);
React.useEffect(() => {
let isMounted = true;
fetchData();
}, [fetchData]);
This is probably an issue of using useCallback and not including certain methods internal to your component in the dependency array. For example, you're using what sounds like setState methods in your callback (setData, setFullData, etc.), which I'm guessing are setState methods on the component in question. If something is causing the component to rerender, those methods are reinitialized, and the reference to setFullData (and the component it references) change on rerender. But because you've used useCallback, a newly rerendered component is calling setState methods that belong to a component instance that is stale / no longer mounted.
Solutions: either don't use useCallback, or include all relevant variables and methods in the dependency array:
const fetchData = React.useCallback(() => {
fetch("http://5sd780beaf65.ngrok.io/api/Data")
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson);
setFullData(responseJson.results);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
setError(err);
});
}, [setData, setFullData, setLoading, setError]);
Make your fetchData function asynchronous then add a await before fetch

how to cancel useEffect subscriptions of firebase

I'm not quite understand how useEffect cleanup function work. Because whatever I do I always get warning:
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:
useEffect(() => {
setLoading(true)
// Get position list
const getPositionList = db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
})
return () => getPositionList
}, [])
useEffect expects as a return value an unsubscribe/cleanup function, or a null. Your .get() is NOT a subscription, so there's nothing to clean up
BUT useEffect is NOT asynchronous, and the .get() definitively returns a promise that needs a delay, even with the .then(). Your dependency array is empty, so useEffect is only called once - but I suspect your component unmounted before the .get() ever returned.
Your code will need to be closer to:
useEffect(() => {
setLoading(true)
(async () => {
// Get position list
await db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
});
)();
return null
}, [])
the ( async () => {})() creates an anonymous asynchronous function, then executes it. You do want to try to structure your system such that the enclosing component does not unmount before loading is set to false - we don't see that here, so we have to assume you do that part right.
The important take-aways:
useEffect is NOT asynchronous
.get() IS asynchronous
( async () => {}) creates an anonymous async function, and ( async () => {})() creates a self-executing anonymous asynchronous function
useEffect(() => {
setLoading(true)
// below firebase api return promise. hence no need to unsubscribe.
db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
setPositionsList(data.list)
setLoading(false)
})
return () => {
// any clean up code, unsubscription goes here. unmounting phase
}
}, [])
According #Dennis Vash answer I figure out, that before setting state I have to check is component mounted or not, so I add variable in useEffect and before set state I check I add if statement:
useEffect(() => {
setLoading(true)
let mounted = false // <- add variable
// Get position list
db.collection('lists').doc('positions').get()
.then( res => {
let data = JSON.stringify(res.data())
data = JSON.parse(data)
if(!mounted){ // <- check is it false
setPositionsList(data.list)
setLoading(false)
}
})
return () => {
mounted = true // <- change to true
}
}, [])

Error: Can't perform a React state update on an unmounted component

I need your help.
I have a component that retrieves data from a REST and when the response returns, I enable the button for the PDF.
const [pdf, setPDF] = useState(false);
const [utente, setUtente] = useState(null);
useEffect(() => {
const url = ""
axios.get(url, {
params: {
id: props.id
}
})
.then((response) => {
setPDF(true);
setUtente(response);
})
.catch((err) => {
setPDF(false);
setUtente(null);
});
return () => {
};
}, [props.id]);
return (
<div className="container">
{
loadPDF
?
<PDFDownloadLink document={<ComponentPDF utente={utente} />} fileName="utente.pdf">
{ <img src="pdf.png" title="PDF" alt="PDF" /> }
</PDFDownloadLink>
:
<React.Fragment/>
}
</div >
);
it works well, but if I go back to the home, sometimes I get 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 the componentWillUnmount method.
in InternalBlobProvider (created by PDFDownloadLink)
can someone help me?
I tried the solutions you indicated, but I still have the same error.
My "loadPDF" value is true, this is because I received the response from AXIOS.
If after receiving the response from AXIOS I wait a few seconds and then i go back with browser, I don't have this error.
if after the AXIOS reply I go back with the browser, I received this and error.
this is because inside Component PDF there is a lot of logic and maybe it takes some time.
do I have to work in ComponentePDF?
What's happening is that React tries to update state when the component is unmounted, so we need to cancel the Axios request when the component unmounts.
I'm gonna destructure id from props for simplicity. Also, you can check Cancellation docs from Axios on how to do it, but I'll leave you the following example:
useEffect(() => {
const url = '';
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios
.get(url, {
params: { id },
cancelToken: source.token,
})
.then((response) => {
setPDF(true);
setUtente(response);
})
.catch((err) => {
setPDF(false);
setUtente(null);
});
return () => source.cancel();
}, [id]);

Resources