Does React keep the order for state updates?
I work on React.
TLDR:
But can you trust React to update the state in the same order as setState is called for
- the same component?
Yes.
- different components?
Yes.
The order of updates is always respected. Whether you see an intermediate state "between" them or not depends on whether you're inside in a batch or not.
In React 17 and earlier, only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.
Starting from React 18, React batches all updates by default. Note that React will never batch updates from two different intentional events (like clicks or typing) so, for example, two different button clicks will never get batched. In the rare cases that batching is not desirable, you can use flushSync
.
The key to understanding this is that no matter how many setState()
calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event. This is crucial for good performance in large applications because if Child
and Parent
each call setState()
when handling a click event, you don't want to re-render the Child
twice.
In both of your examples, setState()
calls happen inside a React event handler. Therefore they are always flushed together at the end of the event (and you don't see the intermediate state).
The updates are always shallowly merged in the order they occur. So if the first update is {a: 10}
, the second is {b: 20}
, and the third is {a: 30}
, the rendered state will be {a: 30, b: 20}
. The more recent update to the same state key (e.g. like a
in my example) always "wins".
The this.state
object is updated when we re-render the UI at the end of the batch. So if you need to update state based on a previous state (such as incrementing a counter), you should use the functional setState(fn)
version that gives you the previous state, instead of reading from this.state
. If you're curious about the reasoning for this, I explained it in depth in this comment.
In your example, we wouldn't see the "intermediate state" because we are inside a React event handler where batching is enabled (because React "knows" when we're exiting that event).
However, both in React 17 and earlier versions, there was no batching by default outside of React event handlers. So if in your example we had an AJAX response handler instead of handleClick
, each setState()
would be processed immediately as it happens. In this case, yes, you would see an intermediate state in React 17 and earlier:
promise.then(() => {
// We're not in an event handler, so these are flushed separately.
this.setState({a: true}); // Re-renders with {a: true, b: false }
this.setState({b: true}); // Re-renders with {a: true, b: true }
this.props.setParentState(); // Re-renders the parent
});
We realize it's inconvenient that the behavior is different depending on whether you're in an event handler or not. In React 18, this is no longer necessary, but before that, there was an API you can use to force batching:
promise.then(() => {
// Forces batching
ReactDOM.unstable_batchedUpdates(() => {
this.setState({a: true}); // Doesn't re-render yet
this.setState({b: true}); // Doesn't re-render yet
this.props.setParentState(); // Doesn't re-render yet
});
// When we exit unstable_batchedUpdates, re-renders once
});
Internally React event handlers are all being wrapped in unstable_batchedUpdates
which is why they're batched by default. Note that wrapping an update in unstable_batchedUpdates
twice has no effect. The updates are flushed when we exit the outermost unstable_batchedUpdates
call.
That API is "unstable" in the sense that we will eventually remove it in some major version after 18 (either 19 or further). You safely rely on it until React 18 if you need to force batching in some cases outside of React event handlers. With React 18, you can remove it because it doesn't have any effect anymore.
To sum up, this is a confusing topic because React used to only batch inside event handlers by default. But the solution is not to batch less, it's to batch more by default. That's what we're doing in React 18.
Using Hooks API: does React respect setState order?
I'm adding my comment here as well since I am unable to add proper formatting to my comment above.
Setting a state via useState
is actually asynchronous, or rather the state change is enqueued and it will then return its new value after a re-render. This means that there is no guarantee in what order the states will be set. They will fire in order, but they may not be set in the same order.
You can read more here: https://dev.to/shareef/react-usestate-hook-is-asynchronous-1hia, as well as here https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/#reacthooksupdatestate
In your case I would use useState
and useEffect
like this:
useEffect(() => {
if(destLong && destLat && !startNav) {
setStartNav(true);
}
}, [destLong, destLat, startNav]);
const startNavigation = (goinglong, goinglat) => {
setDestLong(goinglong);
setDestLat(goinglat);
}
With that said, I think you could further simplify your code by omitting the startNav state altogether and update your conditional render:
{ (destLat && destLong) ?
<MapView
destLong = {destLong}
destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
...
</View>
}
The above should have the same effect since you have two states that are undefined to begin with, and when they are both defined you want to render something and use their values.
And if you want to display the options again you can set the states to undefined again by doing setDestLat(undefined)
and setDestLong(undefined)
How to conditionally update state based on previous state in react functional component?
You are almost there.
const [value, setValue] = useState({})
setValue((prevState) => {
if (condition to not update) {
return prevState;
} else {
return newState;
}
});
You don't necessarily need to do it from the setValue
call. You can also do it like this:
const updateSomething = () => {
if (shouldUpdate)
setValue(newState);
}
In React object updates but does not pass updated object to next function
Try renaming the function as React sees no change in the object and likewise when you are using an array or object in a state. Try to copy them out by storing them in a new variable.
setRecords(
const newRecords = records.map((record) => {
if (record.id === id) {
return { id: id, correct: true };
} else {
return record;
}
})
//seting this now triggers an update
setRecords(newRecords);
);
Then as per react documentation it's better to listen to changes with lifecycle methods and not setting state immediately after they are changed because useState
is asynchronous.
so use useEffect
to listen to the changes to set is Complete
useEffect(() => {
isComplete(records)
}, [records])
I hope this helps you?
Related Topics
Node.Js Plans to Support Import/Export Es6 (Ecmascript 2015) Modules
Render Partial View Using Jquery in ASP.NET MVC
Differencebetween Screenx/Y, Clientx/Y and Pagex/Y
Angularjs Uncaught Error: [$Injector:Modulerr] When Migrating to V1.3
Why Is My React Component Is Rendering Twice
Is ".Then(Function(A){ Return A; })" a No-Op for Promises
Phantomjs Not Waiting for "Full" Page Load
Get the Closest Number Out of an Array
Empty Arrays Seem to Equal True and False at the Same Time
Why Shouldn't Jsx Props Use Arrow Functions or Bind
React/Jsx Dynamic Component Name
How to Resize Images Proportionally/Keeping the Aspect Ratio
Copy Array Items into Another Array
What Is Typescript and Why Would I Use It in Place of JavaScript
Passing Custom Props to Router Component in React-Router V4
Assigning Prototype Methods *Inside* the Constructor Function - Why Not