React Hooks: useState
Published: 2020-09-19 15:59:41


React hooks allow us to create much cleaner code than using class components (up to [90% cleaner](https://youtu.be/dpw9EHDh2bM)).

We'll explore the `useState` hook today by comparing two versions of the same counter component. One of them is written using the class syntax and the other using hooks.

Counter (Class): [https://codepen.io/alveem/pen/VwayxYx](https://codepen.io/alveem/pen/VwayxYx)

Counter (Hooks): [https://codepen.io/alveem/pen/OJNzvqa](https://codepen.io/alveem/pen/OJNzvqa)

![Counter App Image](https://dev-to-uploads.s3.amazonaws.com/i/1miiaf6vvwn8ep6nois9.png)

## Intro

Here's what the class based counter's code looks like:

```jsx
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  
  increase = () => {
    this.setState({ count: this.state.count + 1 })
  }
  
  decrease = () => {
    this.setState({ count: this.state.count - 1 })
  }  
  
  reset = () => this.setState({ count: 0 })
  
  render() {
    return (
      <div className="counter">
        <p className="count">{this.state.count}</p>
        <div className="controls">
          <button onClick={this.increase}>Increase</button>
          <button onClick={this.decrease}>Decrease</button>
          <button onClick={this.reset}>Reset</button>
        </div>
      </div>
    );
  }
}
```

And here's what the `useState` version looks like:

```jsx
const Counter = () => {
  const [count, setCount] = React.useState(0);
  
  const increase = () => setCount(count + 1);
  const decrease = () => setCount(count - 1);
  const reset = () => setCount(0);
  
  return (
    <div className="counter">
      <p className="count">{count}</p>
      <div className="controls">
        <button onClick={increase}>Increase</button>
        <button onClick={decrease}>Decrease</button>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  )
}
```

The `useState` method takes in an initial state value and returns an array where the first element is a reference to the state and the second element is a function to change the state.

Note that we're destructuring the array to make the code cleaner. It's the same as the following:

```jsx
const countStateArray = React.useState(0);
const count = countStateArray[0];
const setCount = countStateArray[1];
```

## Asynchoronicity

Let's see whether the asynchronous behavior of state change is affected by hooks.

### Class Component

We want to increase our counter by `2` instead of `1` now. Let's add another `this.setState` statement to our `increase` method.

```jsx
increase = () => {
  this.setState({ count: this.state.count + 1 })
  this.setState({ count: this.state.count + 1 })
}
```

But our counter still only increases by `1` because `setState` is asynchronous. Since `setState` uses `Object.assign` to assign the state updates it ends up doing this:

```jsx
Object.assign(
  this.state,   // current state { count: 0 }
  { count: 1 }, // first state update
  { count: 1 }, // second state update
)
```

The last object with a matching key passed to `Object.assign` is used to update the original object's value.

Let's try passing a function to `setState` instead:

```jsx
increase = () => {
  this.setState(state => ({ count: state.count + 1 }))
  this.setState(state => ({ count: state.count + 1 }))
}
```

And now our counter increases by `2`.

### Functional Component

Now we want to do the same using `useState`. We'll call `setCount` a second time inside our `increase` method.

```jsx
const increase = () => {
  setCount(count + 1)
  setCount(count + 1)
}
```

And the counter only increases by `1`. So this works the same way as when we passed an object to `setState` in a class component.

Fortunately, the `setCount` function can also has a different form.

```jsx
const increase = () => {
  setCount(prevCount => prevCount + 1)
  setCount(prevCount => prevCount + 1)
}
```

This works now and our counter increases by `2`.

## Differences
There are some differences in the behavior of `setState` and the setter function returned by `useState` (the `setCount` function in our example).

### Receiving props
The `setState` method passes in the props as the second argument to the callback function passed to it. 

`this.setState((state, props) => console.log(props))`

### State Update Behavior
As mentioned above, `setState` merges objects to update state. Hooks replace the entire state.

Let's try to limit our counter's `decrease` method so it doesn't go below `0`.

##### Class Component (setState)
Here's what the method would look in the class component.

```jsx
decrease = () => {
    this.setState(prevState => {
      if (prevState.count <= 0) return;
      this.setState({ count: prevState.count - 1})
    })
  }
```

Notice that on line 3 we're not returning anything. The state still updates since React merges the old state with whatever is returned.

##### Functional Component (useState)
What happens when we do something similar in our functional component?

```jsx
const decrease = () => setCount(prevCount => {
    if (prevCount <= 0) return;
    return prevCount - 1;
  })
```

When the counter is at `0` and we try to decrease it, the number disappears.

The new state is set to `undefined` since whatever we return in callback function passed to `setCount` is set as the new state. And if we try to increase the counter we'll get a `NaN`.

Instead, you need to return the entire new state because new values aren't automatically merged as in `setState`.

```jsx
const decrease = () => setCount(prevCount => {
    if (prevCount <= 0) return 0;
    return prevCount - 1;
  })
```

And now it works as expected.

If you use an object with multiple values in `useState`, a new object needs to be passed in that contains the old key-value pairs along with the new key-value pair(s).

For managing complex state, the [Hooks docs](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables) recommend using `useReducer`.

## Further Readings
1. [React Hooks Docs](https://reactjs.org/docs/hooks-intro.html)
2. [Should I Use Multiple State Variables in `useState`?](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables)
3. [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) (from the docs)
   - Don’t call Hooks inside loops, conditions, or nested functions.
   - Don’t call Hooks from regular JavaScript functions.