-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix internal state consistency of control cards in controlled mode #6771
Conversation
Generate changelog in
|
Add generated changelog entriesBuild artifact links for this commit: documentation | landing | table | demoThis is an automated comment from the deploy-preview CircleCI job. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really don't need an effect here. Excellent example of https://react.dev/learn/you-might-not-need-an-effect
Thank you for the detailed description!
Thanks for the reviews @pgoldberg @styu |
Problem
Repro
In an application using React 18 with the
createRoot
API, a<RadioCard>
in controlled mode may not reflect the current controlledchecked
prop value. Note in the video below how two clicks are required to get the radio button in its checked state.Problem.mov
Here's the code for the video above.
Explanation
The repro code above contains buggy code. It's passing a controlled
checked
prop, but usingonClick
instead ofonChange
to toggle that prop.This causes
<RadioCard>
's internal state tracking to be inconsistent, which is arguably still a Blueprint bug despite the buggy usage this component.With the concurrent renderer
onClick
handler on<RadioCard>
fires.props.checked
value, and triggers theuseEffect
here. The effect changes the button'schecked
state fromfalse
totrue
.blueprint/packages/core/src/components/control-card/useCheckedControl.ts
Lines 25 to 30 in daebef2
<RadioCard>
'sonChange
handler then fires, toggling the updatedchecked
state fromtrue
back tofalse
. 🤦🏻♂️blueprint/packages/core/src/components/control-card/useCheckedControl.ts
Lines 31 to 33 in daebef2
Legacy renderer
With the legacy React 17 renderer, this happened to work because the order of these event handlers are reversed.
onClick
handler on<RadioCard>
fires.onChange
handler fires first.useEffect
fires second.I believe the order is different with the concurrent renderer due to an intentional change to make
useEffect
's timing more consistent.Summary
In summary of the explanation above, Blueprint's uncontrolled state handling is fighting with the controlled state handling of the application.
Changes
The
useEffect
here is an example of "you might not need an effect".blueprint/packages/core/src/components/control-card/useCheckedControl.ts
Lines 26 to 30 in daebef2
I think we can simplify this with a value that's "calculated during rendering".