What happens when using this.setState multiple times in React component?
React batches state updates that occur in event handlers and lifecycle methods. Thus, if you update state multiple times in a <div onClick />
handler, React will wait for event handling to finish before re-rendering.
To be clear, this only works in React-controlled synthetic event handlers and lifecycle methods. State updates are not batched in AJAX and setTimeout
event handlers, for example.
UPDATE
With the release of React v18.0, state updates within non-React events (promises, setTimeout etc.) are also batched by default.
Ref - https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
Why does state not update correctly when calling setState() multiple times
State Updates May Be Asynchronous
Quoted from the reactjs docs about state updates:
React may batch multiple
setState()
calls into a single update for
performance.Because
this.props
andthis.state
may be updated asynchronously, you
should not rely on their values for calculating the next state.
Your second call to addMessage()
uses this.state
to set the new state. But because setState()
is asynchronous the state my not have been updated yet which leads to your second call overriding your first one:
addMessage(message) {
// here you access this.state which hasn't been updated yet in your second call
let messages = this.state.messages.slice();
messages.push({text: message});
this.setState({messages: messages});
}
To fix it, use a second form of
setState()
that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
Use an arrow function that gets passed the previous state including changes of preceding calls to setState()
to calculate the new state. That will fix your issue.
Using es6 spread syntax:
this.setState((prevState, props) => ({
messages: [...prevState.messages, {text: message}]
}));
Using slice()
:
this.setState((prevState, props) => {
let messages = prevState.messages.slice();
messages.push({text:message});
return {messages};
});
setState being called multiple times after await
This happens because as this answer states React tries to batch setState
calls and process them together when it can. But this is not the case with asynchronous computations because React (and anybody in general) can't predict and reproduce the order of setState
's called asynchronously.
So in your case it fallbacks to just updating state 3 times.
React setState called multiple times on the same state object
If you are writing ES2015, you can use the spread operator to copy the whole object and just modify one of it's properties:
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
...this.state.optionA,
option_id: optionID,
on: onOff
}
})
}
Can be very useful when modifying single properties of complex objects on the state tree.
Render Once for Multiple set States using useState hook in React
You can use your states as an object and set them in one instead of separete states like
const [exampleState, setExampleState] = useState(
{fields: {
fieldOne: false,
fieldTwo: false
}
})
You can set in this way
setExampleState({...exampleState, fields: {
fieldOne: true,
fieldTwo: true
},
})
Why is my component in React being called multiple times?
Because React will re-render when state change, if you want stop re-render, put your grabListings()
inside useEffect()
like this:
useEffect(() => {
grabListings();
},[])
Multiple set state within multiple api call inside componentDidMount in ReactJS
You should set state in a single update, updating both values at the same time. Otherwise you are instructing React to initiate two renders, the first would contain users
value and an undefined
value for banks
(depending on your initial state declaration). This render would be quickly followed by a second pass, in which both state values users
and banks
would be defined.
The below example should work as required, in a single render.
Promise.all([
axios.get(<url1>),
axios.get(<url2>)
]).then(([res1, res2]) => {
// call setState here
const users = res1.data.users;
const banks = res2.data.banks;
this.setState({ users, banks });
});
On the other hand, if for some strange requirement you actually want two sequential renders you can use setState
's done callback; example below.
this.setState({ users }, () => {
this.setState({ banks });
});
This will ensure the first render is complete before requesting a new render via setState.
Related Topics
Http Status Code 401 Even Though I'm Sending Credentials in the Request
Ternary Operators in JavaScript Without an "Else"
How to Concatenate Properties from Multiple JavaScript Objects
Is This Simple String Considered Valid JSON
Closure in JavaScript - Whats Wrong
How to Simulate Key Presses or a Click with JavaScript
Understanding Cross-Domain Issue in Iframes
Using Canvas to Animate a Sorting Algorithm in Js
How to Take a Snapshot of HTML5-Javascript-Based Video Player
Possible to Associate Label with Checkbox Without Using "For=Id"
Why Is It Object.Defineproperty() Rather Than This.Defineproperty() (For Objects)
Calling Dynamic Function with Dynamic Number of Parameters
Url Encode a String in Jquery for an Ajax Request
Date VS New Date in JavaScript
Difference Between the 'Controller', 'Link' and 'Compile' Functions When Defining a Directive
Textarea to Resize Based on Content Length