You Are Using the “useState” Hook Wrong
Tags: react, javascript, typescript, webdev
Published: 2023-02-27 04:25:46

React API is not developer friendly. One of them is the useState hook which is another misunderstood API much like useEffect .

It looks like this:

```ts
const [active, setActive] = useState(false);
```

At first glance, it does not look like it is doing anything wrong but if you take a look at how React developers use it you may start to see why:

```ts
const [active, setActive] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [list, setList] = useState([]);
```

The React documentation hints at the practice of individualizing the state into separate `useState` and the community went berzerk with it creating components with unnecessary re-renders for a slow app.

The `useState` hook API mimics the legacy class component state property and `setState` method which was already a bad API.

```ts
class Example extends React.Component {
   constructor(props) {
     super(props);
    
     this.state = {
       active: false,
       loading: true,
       error: null,
       list: [],
       user: null
     };
   }
    
   someAction = () => {
      this.setState({
        ...this.state, 
        active: true, 
        loading: false
      })
   }
    
   render() {
      return (
        ...
      );
    }
}
```

React went from suggesting a grouped update to a single state update knowing that react performance is bad and that every state update is a component re-render (at least re-render calculation).

React already bathes state updates but this only works when inside React event handler. When we have asynchronous stuff which is very popular now, we are better off grouping state updates.

The previous `this.setState` was not friendly because it required spreading the current state or passing a callback which would give you access to the state and props that you can use in your state update.

```ts
this.setState((state, props) => ({
  ...state,
  active: props.isNew, 
  loading: false
}))
```

Developers felt the pain when it came to the spreading state. Especially deep-state spreads.

```ts
this.setState((state, props) => ({
   ...state,
   list: [
      ...state.list,
      props.item
   ], 
   loading: false,
   user: {
      ...state.user,
      name: "John Doe"
   }
}))
```

Deeply spreading the state was tough and that’s why lodash was popular and useful back then. It helped with it! Though it was still better than what we have today because it would only do a single state update which is good for your component.

The similar way to use the useState hook would be like this:

```ts
const [state, setState] = useState<State>({
   active: false,
   loading: true,
   error: null,
   list: [],
   user: null
});
```

This is better for your app because you have the option to make grouped updates which will only re-render your component once instead of calling each action from `useState` . But you still need to spread the state, which is where the pain is.

```ts
setState({
   ...state,
   list: [
     ...state.list,
     props.item
   ], 
   loading: false,
   user: {
     ...state.user,
     name: "John Doe"
   }
})
```

Thankfully, we have `useReducer` hook that we can use to solve this issue and minimize how many times our components have to re-render.

```ts
const [state, setState] = useReducer(
   (prev: State, curr:Partial<State>) => ({...prev, ...curr}), 
   {
     active: false,
     loading: true,
     error: null,
     list: [],
     user: null
});
```

The above alternative allows us to avoid spreading and still solves group update needs. But it does not handle deep object merging requiring spreading the current state to update it. But just for deep objects.

```ts
setState({
   list: [
      ...state.list,
      props.item
   ], 
   loading: false,
   user: {
      ...state.user,
      name: "John Doe"
   }
})
```

So let’s improve this by using a deep object merge utility function:

```ts
const [state, setState] = useReducer(
   (prev: State, curr:Partial<State>) => mergeObjects(prev, curr), 
   {
     active: false,
     loading: true,
     error: null,
     list: []
     user: null
   }
);
```

Where `mergeObjects` can be custom or you can use [lodash merge](https://lodash.com/docs/4.17.15#merge) utility function:

```ts
function mergeObjects(a, b) {
   if(a === null || typeof a !== 'object') return b;
   if(b === null || typeof b !== 'object') return b;
      
   const obj = Array.isArray(a) ? [...a] : a;
      
   for(const key in b) {
     if(b.hasOwnProperty(key)) {
       obj[key] = mergeObjects(obj[key], b[key]);
     }
   }
      
   return obj;
}
```

We can now abstract all this into a new hook:

```ts
const useCompState = <S>(initialState) => {
   const [state, setState] = useReducer(
     (prev: S, curr:Partial<S>) => mergeObjects(prev, curr), 
     initialState);
    
   return {state, setState}
}
```

And then use it like this:

```ts
const {state, setState} = useCompState<State>({
   active: false,
   loading: true,
   error: null,
   list: [],
   user: null
});
    
// grouped update
setState({
   list: [
     ...state.list,
     props.item
   ], 
   loading: false,
   user: {
     name: "John Doe"
   }
})
    
// single update
setState({
   loading: false
});
```

In the above, I only had to spread the array because it's adding an item to the end, but any deep object value would be automatically handled easily.

## Isn't deep-state copy expensive?
It is! This is why it's best to keep the state simple. On the other hand, in modern computers and Javascript engines, the processing power required is next to nothing. Unless you clearly have a performance issue, this should not be an issue.

### Take Away

The mutable nature of the state is probably the worse part, unfortunately not one we can do much about without introducing significant API changes. However, we can improve how we interact with state and props data inside our components.

When handling a state like this with a Typescript project, having a State interface to represent the internal data of your component gives you a nice view of what the component deals with in a single place. It’s much better to maintain the component that way.

The grouped update is always better because it reduces the re-renders which helps avoid weird errors and behaviors of components where React batch updates don't work. Not to mention performance improvements.

Let me know what you think in the comments…

**YouTube Channel**: [Before Semicolon](https://www.youtube.com/channel/UCrU33aw1k9BqTIq2yKXrmBw)
**Website**: [beforesemicolon.com](https://beforesemicolon.com/)