Does React Keep the Order for State Updates

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



Leave a reply



Submit