Why Doesn't React's useState Hook Merge Objects?
Tags: react, javascript
Published: 2022-03-09 15:18:00

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: '' }`.


```jsx
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. 

```jsx
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).

```jsx
<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](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables). 

> 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.