ReactJS: Error while solving memory leak using makeCancellable method - reactjs

I have encountered a memory leak error on in my react application. The error occurs when API call is made. My application renders 3 times because header and footer got setState and then todoList using setState.
Console error 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 the componentWillUnmount method. index.js:1446
I have tried _.isMounted method to solve the issue and worked also but then the solution is deprecated.
isMounted method code below ...
_isMounted = false
componentDidMount() {
this._isMounted = true
API.getTodoList().then(data => {
if (this._isMounted) {
this.setState({ itemList: data.data.itemList });
}
})
}
componentWillUnmount() {
this._isMounted = false
}
Later I tried makeCancelable method to fix memory leak. But it didnt solve the issue and got same memory leak error and another error from .catch()
API call:
// makeCancelable fn is defined at start
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({ isCanceled: true }) : resolve(val),
error => hasCanceled_ ? reject({ isCanceled: true }) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
componentDidMount() {
console.log("didMount")
this.cancelRequest = makeCancelable(
axiosClient.get('/todoList')
.then((response) => {
this.setState({ itemList: response.data.data.itemList })
})
.catch(({ isCanceled, ...error }) => console.log('isCanceled', isCanceled))
)
}
componentWillUnmount() {
console.log("componentUnmount")
this.cancelRequest.cancel();
}
Is there any other way to solve memory leak error without using _.isMounted method.
I would appreciate the help.

The message warns against the possibility of memory leak. It doesn't states that there is one, although original code can result in memory leak, depending on how the request is performed
makeCancelable is misused, it cannot cause the entire promise chain it wraps to not be executed because promises aren't cancellable.
It should be:
this.cancelRequest = makeCancelable(
axiosClient.get('/todoList')
);
cancelRequest.promise
.then(...)
.catch(({ isCanceled, ...error }) => console.log('isCanceled', isCanceled))
There's no need to do this because Axios already provides cancellation:
this.cancelRequest = axios.CancelToken.source();
axiosClient.get('/todoList', { cancel: this.cancelRequest.token })
.then(...)
.catch(error => console.log('isCanceled', axios.isCancel(error)))

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 Leak, issues with ComponentWillUnmount / Axios

I am performing an API call that kicks off with componentDidMount, however when a new component is loaded by the user, there is a notification for a potential memory leak, see below for message. I have researched different solutions but have found nothing yet that works, I'm wondering if this can be fixed within this particular component with a componentWillUnmount or if it is better handled within the axios call itself.
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.
componentDidMount() {
this.loadBackground();
this.getUpdatedWeather();
this.getNewMartianPhotos();
}
checkMartianPhotos = () => {
if (this.state.martianMotion) {
console.log('still shooting');
this.getNewMartianPhotos();
} else {
return console.log('done now');
}
};
getNewMartianPhotos = () => {
let loadedImage = '';
let loadedInfo = '';
let loadedMeta = '';
let totalImage;
API.getMarsPhotos().then(data => {
// console.log(data.data);
// console.log(
// data.data.collection.items[this.state.martianCount].data[0].photographer
// );
// console.log(
// data.data.collection.items[this.state.martianCount].data[0].description
// );
// console.log(
// data.data.collection.items[this.state.martianCount].links[0].href
// );
totalImage = data.data.collection.items.length;
loadedImage =
data.data.collection.items[this.state.martianCount].links[0].href;
loadedInfo =
data.data.collection.items[this.state.martianCount].data[0].description;
loadedMeta =
data.data.collection.items[this.state.martianCount].data[0]
.photographer;
this.setState({
martianImage: loadedImage,
martianDescription: loadedInfo,
martianMeta: loadedMeta,
martianCount: this.state.martianCount + 1
});
if (this.state.martianCount < totalImage) {
console.log(
`shooting off, image count now ${this.state.martianCount} against ${totalImage}`
);
setTimeout(this.checkMartianPhotos, 10000);
}
});
};
componentWillUnmount() {
clearTimeout(this.checkMartianPhotos);
}
-------------------------------------
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
getMarsPhotos: () =>
axios
.get('https://images-api.nasa.gov/search?q=martian', {
cancelToken: source.token
})
.catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('request canceled', thrown.message);
} else {
console.log('there is an error that needs to be handled');
}
})
As the error informs, your component is calling setState after it has been unMounted. This is because you are clearing your timeout incorrectly. You should be doing the following to properly clear your timeout on unmount.
this.id = setTimeout(this.checkMartianPhotos, 10000);
And then clear with
clearTimeout(this.id)
In componentWillUnmount
Also, try to move your async logic (api calls) out of your component.
Refer to this to see how to stop setting a state after an API call on unmounted component.

reactJs page redirect

I am getting data with API using React. But when I redirect, I get an error, can you help?
Thank you so much.
Very thanks
componentDidMount() {
fetch(`${this.domain}/api/debt/list?customer=` + this.props.customerInfo.customer.ID, {
headers: {
Authorization: `Bearer ${localStorage.getItem('id_token')}`,
"Content-Type": "application/json",
}
})
.then(res => {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => Promise.reject(err));
}
})
.then(json => {
this.setState({
items: json
});
// console.log(json)
})
.catch(error => {
//console.log('request failed:', error);
return error;
});
}
render() {
const { isLoaded, items } = this.state;
if (this.props.customerInfo.customer.ID === "-1") {
return <Redirect to={"/customerlist"}/>
}
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 AuthWrapped (created by ConnectFunction)
in ConnectFunction (at DefaultLayout.js:73)
in Route (at DefaultLayout.js:68)
I believe this happens when you have this.props.customerInfo.customer.ID === "-1" true. In this case, you are redirecting, but the API call you made is still pending and upon it's completion you are using setState on a component which does not exist since you already redirected.
1) Easiest way to fix this is before using setState put a conditional check for the case in which you have redirected or better you have a separate flag to check this.
constructor(){
this.isComponentDestroyed = false;
}
componentDidMount(){
fetch(...)
.then(()=>{
if(!this.isComponentDestroyed){
this.setState({
items: json
});
}
})
}
componentWillUnmount(){
this.isComponentDestroyed = true;
}
2) You can also check on how to cancel the fetch call. Ref How do I cancel an HTTP fetch() request?
From the comments, adding a reference to How to cancel a fetch on componentWillUnmount

React Native - cancel all subscriptions and asynchronous tasks in the componentWillUnmount method

Since my app is fetching images from API and rendering the result as expected. But showing this warning is incomplete to this project and given answers aren't solved out my issue.
Moreover, it couldn't be solved with AbortController to pass the signal as a parameter in fetch call and using AbortController.abort() in componentWillUnmount
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.
CODE:
componentDidMount() {
this.getImage(Flikr_URL);
}
getImage(url) {
fetch(url)
.then(response => response.json())
.then(responseJson =>
this.setState({
imageData: responseJson.photos.photo,
loading: false
})
)
.catch(err => {
console.log(err);
throw error;
});
}
componentWillUnmount() {
this.getImage();
}
If you want simple solution, this will help you. May be another good solution will be there, but for now you can do like this.
Check manually component is mounted or not.
then in componentDidMount method set flag componentMounted to true.
componentDidMount() {
this.componentMounted = true;
this.getImage(Flikr_URL);
}
getImage(url) {
fetch(url)
.then(response => response.json())
.then(responseJson => {
if (this.componentMounted) { // check here component is mounted
this.setState({
imageData: responseJson.photos.photo,
loading: false
});
}
})
.catch(err => {
console.log(err);
throw error;
});
}
In componentWillUnmount method set flag to false
componentWillUnmount() {
this.componentMounted = false;
}

How to use setState in an asynchronous function

I am running this code:
.then((url) => {
if (url == null || undefined) {
return this.props.image;
} else {
const { image } = this.props;
//entryUpdate is an action creator in redux.
this.props.entryUpdate({ prop: 'image', value: url })
.then(() => {
this.setState({ loading: false });
});
but I get the following error:
How do I format setState() inside an asynchronous function that's called after an action creator?
Any help would be much appreciated!
In order for this to work, your action creator this.props.entryUpdate would need to return a promise for the async work it's doing. Looking at the error message, that does currently not appear to be the case.
You also need to be aware that calling setState() in the asynchronous callback can lead to errors when the component has already unmounted when the promise resolves.
Generally a better way is probably to use componentWillReceiveProps to wait for the new value to flow into the component and trigger setState then.
I placed the .then() function inside of the the if statement. But it should be like this:
.then((url) => {
if (url == null || undefined) {
return this.props.image;
} else {
const { image } = this.props;
this.props.entryUpdate({ prop: 'image', value: url })
}
})
.then(() => {
this.setState({ loading: false });
});

Resources