onChange doesn't fire if input re-renders due to a setState() in a non-React capture phase listener
See original GitHub issueExtracting from https://github.com/facebook/react/issues/12643.
This issue has always been in React. I can reproduce it up to React 0.11. However it’s probably extremely rare in practice and isn’t worth fixing. I’m just filing this for posterity.
Here is a minimal example.
class App extends React.Component {
state = {value: ''}
handleChange = (e) => {
this.setState({
value: e.target.value
});
}
componentDidMount() {
document.addEventListener(
"input",
() => {
// COMMENT OUT THIS LINE TO FIX:
this.setState({});
},
true
);
}
render() {
return (
<div>
<input
value={this.state.value}
onChange={this.handleChange}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Typing doesn’t work — unless I comment out that setState
call in the capture phase listener.
Say the input is empty and we’re typing a
.
What happens here is that setState({})
in the capture phase non-React listener runs first. When re-rendering due to that first empty setState({})
, input props still contain the old value (""
) while the DOM node’s value is new ("a"
). They’re not equal, so we’ll set the DOM node value to ""
(according to the props) and remember ""
as the current value.

Then, ChangeEventPlugin
tries to decide whether to emit a change event. It asks the tracker whether the value has changed. The tracker compares the presumably “new” node.value
(it’s ""
— we’ve just set it earlier!) with the lastValue
it has stored (also ""
— and also just updated). No changes!

Our "a"
update is lost. We never get the change event, and never actually get a chance to set the correct state.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:9
- Comments:13 (3 by maintainers)
Instead of using this
onChange={this.handleChange}
, you can useonChange={event => this.handleChange(event.target.value)} />
Oh man, I wish I’d stumbled across this thread earlier - was banging my head against this issue for a while and thought I was going insane.
I managed to reproduce my simplified use case here.
I have an application where I’ve started to add in keyboard shortcuts, hence why I was making use of
document.addEventListener
as well as other form inputs on the page.What I was seeing in my app while I was trying to debug this really confused me. When I added
debugger
statements within the relevantuseEffect
hook I could see the text appear in the input then disappear within theuseEffect
hook’s cleanup function - all without firing the input’sonChange
.While I appreciate that it might not be pragmatic to try and fix what might be considered an “extremely rare” issue; how realistic would it be to add a warning for users about this potential conflict? Maybe even just adding a “N.B.” to the documentation?
When I searched for “addEventListener” on the reactjs.org site it took me to Handling Events. Maybe a note here could save people some hassle? Or making a “Gotchas” page for known edge-case issues like this that are known but aren’t going to be fixed anytime soon?
In any case, thanks for filing this issue in the first place - it gave me some much needed closure!