When I use fetch in setState the function makes two network requests, but I expect one request.
Why is this happening and how to prevent it?
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data)
});
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Another version with setState in the fetch. Now I have one network call, but two values in my state after one click:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
this.setState((state) => {
state.newItems.push("value")
})
console.log(this.state)
});
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Ok, basically it has this effect in this example as well:
import React from 'react';
class TestFetch extends React.Component {
constructor(props) {
super(props);
this.state = {
'newItems': []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => {
state.newItems.push("value")
})
console.log(this.state);
}
render() {
return (
<button onClick={this.handleClick}> Test </button>
)
}
}
export default TestFetch
Why is this happening...
My guess would be you are rendering your app into a React.StrictMode
component. See Detecting unintentional side-effects
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
- Class component
constructor
,render
, andshouldComponentUpdate
methods- Class component static
getDerivedStateFromProps
method- Function component bodies
- State updater functions (the first argument to
setState
)- Functions passed to
useState
,useMemo
, oruseReducer
In other words, the setState
is called twice by React to help you find unintentional side-effects, like the double fetching.
...and how to prevent it?
Just don't do side-effects in the setState
callback function. You likely meant to do the fetch
and in the Promise chain update state.
handleClick() {
fetch('http://example.com/', {
mode: 'no-cors'
})
.then(data => {
console.log(data);
this.setState( ......); // <-- update state from response data
});
}
Another version with setState in the fetch. Now I have one network call, but two values in my state after one click:
In your updated code you are mutating the state object. Array.prototype.push
updates the array by adding the new element to the end of the array and returns the new length of the array.
this.setState(state => {
state.newItems.push("value") // <-- mutates the state object
})
I believe you see 2 new items added for the same reason as above. When updating arrays in state you need to return a new array reference.
You can use Array.prototype.concat to add the new value and return a new array:
this.setState(prevState => {
newItems: prevState.newItems.concat("value"),
});
Another common pattern is to shallow copy the previous state array into a new array and append the new value:
this.setState(prevState => {
newItems: [...prevState.newItems, "value"],
});
Additionally, once you sort out your state updates, the console log of the state won't work because React state updates are asynchronously processed. Log the updated state from the componentDidUpdate lifecycle method.
componentDidUpdate(prevProps, prevState) {
if (prevState !== this.state) {
console.log(this.state);
}
}