Bug: Bubbled onFocus handler that triggers an update prevents onChange handler in createRoot
See original GitHub issueReact version: I think any experimental version. Tested using:
0.0.0-experimental-5faf377df
and0.0.0-experimental-e5d06e34b
at least.
Steps To Reproduce
- Open CodeSandbox: https://codesandbox.io/s/createroot-broken-focus-onchange-ysrut?file=/src/App.js
- Attempt to click on the label text for the checkbox in the createRoot section
- Notice that the checkbox won’t check -
onChange
isn’t called - Attempt to click on the label text for the checkbox in the render section
- Notice that the checkbox will check -
onChange
is called
Link to code example: https://codesandbox.io/s/createroot-broken-focus-onchange-ysrut?file=/src/App.js
The current behavior
When rendering using createRoot
:
onFocus
is calledonChange
is not called
When rendering using render
:
onFocus
is calledonChange
is called
My coworker and I spent a bit of time debugging this, if we at all schedule the onFocus
setState handler (using setTimeout
, or requestAnimationFrame
for example), then onChange
is always called from the input.
e.g.
handleFocus = () => {
setTimeout(() => this.setState({ isHovered: true }), 0)
// or
requestAnimationFrame(() => this.setState({ isHovered: true }))
}
Additionally, if the label is a sibling to the input:
return (
<>
<input id="id" onChange={} type="checkbox" checked={} />
<label htmlFor="id" onFocus={}>
label text
</label>
</>
)
then the onChange
handler will be called as well (Note: onFocus
won’t be called in this case because there isn’t a focusable element within it).
Also worth noting, re-implementing the checkbox component using hooks will still run into the same bug - no differences between using classes vs hooks.
The expected behavior
Calling setState
in onFocus
shouldn’t prevent onChange
on a nested input element to be called. Both onFocus
and onChange
should be called.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:7 (6 by maintainers)
This does not appear reproducible in a sandbox using the build from https://github.com/facebook/react/pull/20792. That PR enables some flags that aren’t enabled yet, but will be closer to the release. Essentially, they make discrete events (including checkbox clicks) flush in a microtask.
https://codesandbox.io/s/createroot-broken-focus-onchange-forked-9195f?file=/src/index.js
I think we can close this since it’s not directly actionable, but it will take some time before this behavior gets to an
experimental
build because we need to do more testing. Thanks for raising this!Thanks for the detailed bug report @hamlim. I verified the same behavior with the latest experimental release as well (
0.0.0-experimental-e5d06e34b
). You can temporarily get around this by flushing the state update inhandleFocus
synchronously usingReactDOM.flushSync