I saw a question today about React's useState
hook, as there is unexpected behavior compared to this.setState
in class components.
Expected Behavior
A user inputs a value and this.setState
merges both initial state and the dispatched object from the event handler.
So, if a user types the letter a
, state is represented as the merger of,
{ name: 'a' }
and { name: '', email: '', phone: '' }
,
as { name: 'a', email: '', phone: '' }
.
export default class ControlledForm extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
email: '',
phone: ''
}
}
render() {
return (
<form onSubmit={e => e.preventDefault()}>
<fieldset>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={this.state.name}
onInput={event => this.setState({ name: event.target.value })}
// { name: 'a', email: '', phone: '' }
/>
</fieldset>
</form>
)
}
}
Unexpected Behavior?
A user inputs a value as before. However, setFormValues
dispatches an object,
{ name: 'a' }
, replacing the initial state object.
function ControlledForm() {
const [formValues, setFormValues] = useState({
name: '',
email: '',
phone: ''
})
return (
<form onSubmit={e => e.preventDefault()}>
<fieldset>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={formValues.name}
// { name: 'a' }
onInput={event => setFormValues({ name: event.target.value })}
/>
</fieldset>
)
}
Manually Merging Objects
setState
or setFormValues
in this context, is a function with a parameter that can be a plain value, an object, or a function with a parameter containing its current state.
We can leverage the function parameter to merge our objects (or arrays).
<input
type="text"
id="name"
value={formValues.name}
// { name: 'a', email: '', phone: '' }
onInput={event => setFormValues(values => ({ ...values, name: event.target.value }))}
/>
Why though?
This may feel like a jarring developer experience for those migrating from legacy React code but, this is by design.
If you miss automatic merging, you could write a custom useLegacyState Hook that merges object state updates. However, we recommend to split state into multiple state variables based on which values tend to change together
In other words, it may be more convenient to avoid merging objects altogether. Would one need to merge objects if replacing the previous object achieved the same result?
So, if one has to merge state objects, they have a few options at their disposal.
- Manual merging of objects within state
- Individual
useState
hooks for each object property - A reducer (which may be a tad too much for this example)
When working with state, I tend to ask myself, do these variables change together? If yes, I'll go with an object, and know I'll be fine.
Top comments (0)