Fix ComboBox re-opening after selection #5354
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #5120
This fixes ComboBox re-opening after selecting an option in the RAC docs. The problem stems from how React batches updates, and how our collection implementation works. Our collection implementation uses React's
useSyncExternalStore
hook to trigger updates. Inside ComboBox, we have alastValue
ref, and aninputValue
state, which are updated simultaneously. Then, in the next render, we assume thatlastValue.current
will equalinputValue
. However, this assumption is not true when some ancestor component usesuseSyncExternalStore
, which triggers a synchronous update. Essentially,setState
is asynchronous, but a synchronous update can preempt it. In that case, the ref will already be updated but the state will not. This CodeSandbox has a minimal reproduction.The solution is to move
lastValue
to state instead of a ref. There are probably a lot more instances of this pattern that we should audit throughout the codebase, but this is the first real world case where this assumption turned out to be incorrect.This started appearing in our docs due to the change from
ReactDOM.render
toReactDOM.createRoot
.render
behaves like React 17, so state updates run synchronously.createRoot
opts-into concurrent features.