TLDR;
- React
setState()
is asynchronous. - React
SyntheticEvent
cannot be accessed asynchronously.
Problems Using event.target
Within setState()
I came across an unexpected console error and warning when attempting to use event.target
within setState()
.
Uncaught TypeError: Cannot read property 'value' of null
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property 'target' on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist().
An example of the code I was attempting to run was: (Open up the console and try to run the demo in preview view to see the error)
React Event System: SyntheticEvent
It turns out React has it's own event system for event handling, using SyntheticEvent
.
React's SyntheticEvent
wraps around the browser's native event to provide cross-browser compatibility support. Instead of passing in the native event to React event handlers, an instance of this SyntheticEvent
is passed in.
The console warning I encountered occurs because React re-uses the SyntheticEvent
object for performance reasons, by pooling the synthetic events all together. Thus, all the properties on event.target
are nullified after an event callback is invoked.
Essentially, SyntheticEvent
cannot be used asynchronously, because the event will no longer exist after the event callback has been invoked.
This is a problem, knowing that React's setState()
behavior is asynchronous.
However, what if I want to use event.target
within setState()
?
handleChange(e) {
this.setState((prevState, props) => {
return {
username: e.target.value // Attempting to use e.target.value // within setState() }
})
}
render() {
return (
<input
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
)
}
}
Solution 1: Use React event.persist()
Using event.target
to construct a new state is a common pattern, and React has provided a solution with event.persist()
.
Calling event.persist()
on the event removes the synthetic event from the pool and allows references to the event to be retained asynchronously.
js
handleChange(e) {
e.persist();
this.setState((prevState, props) => {
return {
username: e.target.value
}
})
}
Solution 2: Cache event.target
Another solution is to cache the result of event.target
within the event handler, and reference this cached value within the setState()
callback.
handleChange(e) {
let inputValue = e.target.value; // Cache the value of e.target.value
this.setState((prevState, props) => {
return {
username: inputValue
}
})
}