I'm using React (18.2.0) and I'm trying to create a page where I enter text into a textbox, and the page immediately updates on-page elements using that new text. The minimum example looks like this:
The following code works correctly:
import React from "react";
class App extends React.Component {
state = {
name: "INITIAL MESSAGE",
};
onChangeEventHandler = (eventInfo) => {
this.setState((state, props) => ({ name: eventInfo.target.value }))
};
render() {
return (
<div className="App-header">
<p>Welcome {this.state.name}</p>
<input
type="text"
value={this.state.name}
onChange={this.onChangeEventHandler}
/>
</div>
);
}
}
export default App;
On my way to this solution I tried to do the same thing except that I used currentTarget
instead of target
:
this.setState((state, props) => ({ name: eventInfo.currentTarget.value }))
This version crashes as soon as I try to change the text, complaining about react-dom.development.js:11340 Uncaught TypeError: Cannot read properties of null (reading 'value')
.
It looks like it's crashing while trying to do the currentTarget.value step in the lambda function; doing that step in the event handler function and storing the result into a local works great:
onChangeEventHandler = (eventInfo) => {
let newName = eventInfo.currentTarget.value
this.setState((state, props) => { return { name: newName } });
};
My question is this: why does eventInfo.currentTarget.value
work fine in the event handler function, but crashes when it's in the lambda function (which React calls later as a callback)? In particular, it seems like the lambda should be a closure and have access to the eventInfo object.
One last, even more confusing fact: If I instrument the code as follows, it appears that eventInfo.currentTarget is null in both places. Why does it crash in the lambda but do fine in the function?
onChangeEventHandler = (eventInfo) => {
console.log("onChangeEventHandler", eventInfo)
let newName = eventInfo.currentTarget.value
this.setState((state, props) => { console.log("lambda", eventInfo); return { name: newName } });
//this.setState((state, props) => ({ name: eventInfo.target.value }))
};
Output:
log.js:24 [HMR] Waiting for update signal from WDS...
App.js:10 onChangeEventHandler SyntheticBaseEvent {currentTarget: null,_reactName: 'onChange', _targetInst: null, type: 'change', nativeEvent: InputEvent, target: input, …}bubbles: truecancelable: falsedefaultPrevented: falseeventPhase: 3isDefaultPrevented: ƒ functionThatReturnsFalse()isPropagationStopped: ƒ functionThatReturnsFalse()isTrusted: truenativeEvent: InputEvent {isTrusted: true, data: 'a', isComposing: false, inputType: 'insertText', dataTransfer: null, …}target: inputtimeStamp: 5467.5999999940395type: "change"_reactName: "onChange"_targetInst: null[[Prototype]]: Object
App.js:12 lambda SyntheticBaseEvent {currentTarget: null, _reactName: 'onChange', _targetInst: null, type: 'change', nativeEvent: InputEvent, target: input, …}bubbles: truecancelable: falsedefaultPrevented: falseeventPhase: 3isDefaultPrevented: ƒ functionThatReturnsFalse()isPropagationStopped: ƒ functionThatReturnsFalse()isTrusted: truenativeEvent: InputEvent {isTrusted: true, data: 'a', isComposing: false, inputType: 'insertText', dataTransfer: null, …}target: inputtimeStamp: 5467.5999999940395type: "change"_reactName: "onChange"_targetInst: null[[Prototype]]: Object
CodePudding user response:
This is quite a question - my best theory is this:
Since states are async, there's a disconnect between the way Event.currentTarget
works and how long it's taking to update the state.
Taking a look at MDN, there's actually a specific section about Event.currentTarget
which says this:
Note: The value of event.currentTarget is only available while the event is being handled. If you console.log() the event object, storing it in a variable, and then look for the currentTarget key in the console, its value will be null. Instead, you should either use console.log(event.currentTarget) to be able to view it in the console or use the debugger statement, which will pause the execution of your code thus showing you the value of event.currentTarget.
Given this, my theory is that you're getting the type error because by the time that setState tries to work - Event.currentTarget
has no value since it's been resolved. As MDN says, it only carries a value for the life of the event which likely expires too fast for setState to work as intended. The true value would then instead be in Event.target
, as you're seeing.
I suspect the variable part doesn't cause a crash since it's not async and it's able to store the null
value. They even somewhat mention this in the above block quote which would explain why your instrumentation shows null.