Reputation: 662
I am using the useState hook for functional components where I am facing a strange behavior. I am using the useState like this.
const [state, setState] = useState({ title: "", amount: '' });
Same state I am binding with form data. If I use to like this
<input type="text" id="title" value={state.title}
onChange={event => setState( { ...state, title: event.target.value })} />
then it works perfectly, but if I use the previous State for real-time state like this
<input type="text" id="title" value={state.title}
onChange={event => setState((prevState) => {
return {
...prevState,
title: event.target.value
}
})
} />
then event get loose on the second keypress. On research, I have found that it is due to event pooling and I can use event.persist() for that. My question is, why is it works in 1st scenario I explained and why not in 2nd?
Upvotes: 1
Views: 824
Reputation: 662
What actually here is happening is due to closure and the way how React handle the event. function under setState is closure function, so the value of event gets locked on first execution, and closure tries to fetch the first value(locked value). React implements Event pooling in which, event object will be nullified after event callback is invoked.
but React also gives the way to pull out the event object from event pooling with persist method as below.
`onChange={event => {
event.persist();
setState(prevSt....`
By this, React will never nullify the object, so closure will always have the access to event method.
Upvotes: 1
Reputation: 7480
In the first example, you're reading the event data directly in the event handler, while the event is still current. In the second example however, you're reading it in the callback you pass to setState()
. This callback gets invoked asynchronously when the state has been updated (that's the whole point of it) by which time the event handler has already returned and the native event (wrapped by React's SyntheticEvent) has been de-referenced. This is what you prevent by invoking event.persist()
.
Upvotes: 0
Reputation: 1661
So in setState( { ...state, title: event.target.value })
you are spreading the state which means maintaining the current state and changing the title
of the state object to the current value in the input
. while in
setState((prevState) => {
return {
...prevState,
title: event.target.value
}
})
event pooling is used so the prevState
is event data(in this case the state object) which is sent as a callback, so when you enter the first character the event object is in effect being sent back to the pool and by the time you enter the second character the event object will have already been returned to the pool and hence event would be null
. so to overcome this you can make the event persistent by using the event.persist()
which would allow you to use the event properties in an asynchronous way, hope this helps
<input
type='text'
id='title'
value={state.title}
onChange={event => {
event.persist();
setState(prevState => {
return {
...prevState,
title: event.target.value
};
});
}}
/>
Upvotes: 0
Reputation: 6558
Because in the second iteration, Event parameter gets undefined. So it can't find the value it throws null exception. You can simply overcome the situation by passing an arrow function
// Get a hook function
const {useState} = React;
const Example = ({title}) => {
const [count, setCount] = useState(0);
const [state, setState] = useState({ title: "", amount: '' });
const setValue = (value) => {
setState((prevState) => {
console.log(prevState);
return {
...prevState,
title: value//(event.target?event.target.value : 'val')
}
})
}
return (
<div>
<input type="text" id="title" value={state.title}
onChange={event => setState({ ...state, title: event.target.value })} />
<input type="text" id="title" value={state.title}
onChange={event => setValue(event.target.value)}
/>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="Example using Hooks:" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Upvotes: 0