React version: I think any experimental version.
Tested using:
0.0.0-experimental-5faf377df and0.0.0-experimental-e5d06e34b at least.onChange isn't calledonChange is calledLink to code example: https://codesandbox.io/s/createroot-broken-focus-onchange-ysrut?file=/src/App.js
When rendering using createRoot:
onFocus is calledonChange is not calledWhen rendering using render:
onFocus is calledonChange is calledMy 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.
Calling setState in onFocus shouldn't prevent onChange on a nested input element to be called. Both onFocus and onChange should be called.
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 in handleFocus synchronously using ReactDOM.flushSync
ReactDOM.flushSync(() => {
this.setState({ isHovered: true })
})
Is this the same as https://github.com/facebook/react/issues/19385?
Yea, that one looks very similar and I think it could be the same issue
Note that as a temporary workaround, this fixes it:
handleFocus = (e) => {
ReactDOM.flushSync(() => {
this.setState({ isHovered: true });
});
}
Most helpful comment
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 inhandleFocussynchronously usingReactDOM.flushSync