question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bug: is the current `useSyncExternalStore` batching & flushing behaviour intended?

See original GitHub issue

React version: 18

Link to code example:

CodeSandbox

The expected behavior

Let’s assume we have a increment function that first increments a local value, then a uSES value and then another local value like this:

function increment() {
    setState1((x) => x + 1);
    setUsesState((x) => x + 1);
    setState2((x) => x + 1);
  }

Now, there would be two ways this could behave that would be “intuitive for me”:

  1. everything is batched: [state1, usesState, state2] goes from [0,0,0] to [1,1,1]
  2. it batches until the “sync update” flushes the current batch: [state1, usesState, state2] goes from [0,0,0] to [1,1,0] to [1,1,1]

The current behavior

Now, actual behaviour is different in React 18, depending on the “mode” React is currently in.

  1. in an event handler, everything is batched [0,0,0] to [1,1,1] - no problem here
  2. outside an event handler, the uSES setter is flushed first, then the local state changes are batched. [0,0,0] becomes [0,1,0] becomes [1,1,1] - this is very unintuitive for me.
  3. even inside a manual call wrapped in unstable_batchedUpdates, we go [0,0,0] -> [0,1,0] -> [1,1,1]

Point 3 means that there is actually no way to even manually batch an update by uSES - but looking at point 1, React sometimes does so internally.

It seems that even in the non-batched situations, React does some batching: Calling setUsesState twice before calling setState2 will not lead to a [0,0,0] -> [0,1,0] -> [0,2,0] -> [1,2,1] situation, but to [0,0,0] -> [0,2,0] -> [1,2,1]

Up until now we had assumed that uSES would always behave like in 1., and we were only made aware of this by bug reports on react-redux.

Is this intended behaviour or a bug?

There might be some high priority update thing with a transition that I am missing here though - but either way this feels very breaking from older behaviour to me - and yes, I know that batchedUpdates has the unstable prefix 😉

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:2
  • Comments:16 (9 by maintainers)

github_iconTop GitHub Comments

3reactions
sebmarkbagecommented, Sep 13, 2022

We discussed with the team and we agreed that it makes sense to change this. Now it’s just a matter of implementing it and when someone can get around to it.

While React treats these as separate lanes, the programming model only has “Sync” and “Transition” so it makes sense to treat these all as Sync and flush them all together at the last possible opportunity but no later than the earliest heuristic.

If something is wrapped in startTransition only the setStates will be delayed, and any uSES will be flushed early. I thought that case was even a warning? Maybe we should add back the warning.

1reaction
Andaristcommented, Sep 5, 2022

Hm, while priority-based updates are probably a powerful feature - it’s really hard to grasp how this works in edge cases like this. It feels like there should be a way to somehow “join” those updates without resorting to flushSync. In this case, it would be quite convenient to change the priority of the update with the default priority if there is already an ongoing “sync” update. Correct me if I’m wrong but the uSES update is still not exactly synchronous - all updates coming from that store are batched together and more often than not it is desirable to flush other updates with those. Part of the problem is that people usually won’t even interact with uSES directly but rather through a library. In those situations, it’s even harder to notice that one might deal with such a discrepancy in flushed updates.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Twitter-এ Sebastien Lorber • ⚛️ ThisWeekInReact.com ...
1: Optimize renders with useSyncExternalStore 2: useSyncExternalStore ... Bug: is the current `useSyncExternalStore` batching & flushing behaviour intended?
Read more >
use-sync-external-store | Yarn - Package Manager
useSyncExternalStore is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. It...
Read more >
React v18.0 – React Blog
Batching is when React groups multiple state updates into a single re-render for better performance. Without automatic batching, we only  ...
Read more >
React v18.0 - Hacker News
"Modern Redux" with Redux Toolkit and the React-Redux hooks API is _much_ easier ... Do you know of an issue that's tracking a...
Read more >
A (Mostly) Complete Guide to React Rendering Behavior
Note: Updated October 2022 to cover React 18 and future React updates ... That's a reference to this render batching behavior.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found