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

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;
}

Related

Memory leak warning in react component using hooks

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])

REACT JS class Lifecycles: How to put http request in ComponentdidUpdate?

I have a http request to call once the prop taken from redux store updates as shown below:
const mapStateToProps = state => {
console.log(state.queryBuild);
return {
queryBuilderObject: state.queryBuild,
}
}
export default connect(mapStateToProps, null)(SummaryView);
Here is my componentdidupdate function:
async componentDidUpdate()
{
//console.log("Component unmount detected");
//console.log(this.props.queryBuilderObject);
this.setState({state: {
...this.state,
isLoading: true,
}});
await axios.post(ApiEndPoints.getSummaryDataByQueryBuilder,this.props.queryBuilderObject,{timeout: axiosTimeOut})
.then(response => {
console.log("REsponse:");
console.log(response);
this.setState({state: {
...this.state,
isLoading: false,
}});
})
.catch(error => console.log("Error: " + error.message));
}
now here's the problem... somehow I want to only make an http request if props.queryBuilderObject changes that comes from redux store. But when I am going this way, I am entering into an infinite loop as I am setting state and hence componentdidupdate is triggered everytime.
Can someone suggest the right way to do so?
componentDidUpdate receives the previous props and state as arguments, you can check the previous props' queryBuilderObject against the current props' queryBuilderObject and if they are not equal do the POST request.
componentDidUpdate(prevProps, prevState, snapshot)
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition
If you update state from this lifecycle function without a conditional check then it will likely cause infinite render looping.
There is also no need to spread in existing state in the setState function; setState does a shallow merge of state updates.
async componentDidUpdate(prevProps) {
if (prevProps.queryBuilderObject !== this.props.queryBuilderObject) {
this.setState({ isLoading: true });
await axios
.post(
ApiEndPoints.getSummaryDataByQueryBuilder,
this.props.queryBuilderObject,
{ timeout: axiosTimeOut }
)
.then((response) => {
this.setState({ isLoading: false });
})
.catch((error) => console.log("Error: " + error.message));
}
}

How to send updated state in axios in React?

I am trying to send post request using axios in Reactjs.
I have two component a timer component and App component and in App component i am trying to submit a form and send an axios call when i fetch the time from Timer component and save itinto counter state
I have written a condition if counter is true then update my state and then further send the post request
Working Demo
here is a handle submit code:
const handleSubmit = e => {
console.log("handleSubmit");
e.preventDefault();
if (counter) {
console.log(counter);
const url = `url string`;
setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
});
console.log(state);
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}
};
The problem is when counter is true its not update the state which causes error while send axios request.
I have consoled each and every thing but still it fails.
It seems there is lot of rendering.
If you are using class components, you can make the reuqest after the state has been set. Something like this:
this.setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
}, () => {
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
});
Since you did set the react-hooks tag, I guess that approach is not what you need. In your case, I suggest saving new state in some temporary variable and than passing that variable to axios. Like this:
const newState = {
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
};
setState(newState);
axios
.post(url, newState)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
setState can be executed asynchronously by React, to optimize the rendering process. For cases like this one, it can also take a callback function that is guaranteed to be executed after updating the state.
For example:
this.setState({
name:'value'
},() => {
console.log(this.state.name);
});
in this case console.log will be executed after setting the name variable.
see the docs: https://reactjs.org/docs/react-component.html#setstate

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

ReactJS: Error while solving memory leak using makeCancellable method

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

Resources