Should you useState or useReducer 🤷? Doesn't matter really.  Explained in 3 mins.
Tags: react, javascript, webdev, productivity
Published: 2019-09-04 15:24:22

The React [doc](https://reactjs.org/docs/hooks-reference.html#usereducer) recommends `useReducer` for handling complex state values. But to me they are equally powerful. Let me show you how.

# useReducer can replace useState

First, the simpler case: any `useState` can be implemented using `useReducer`. In fact, the `useState` hook itself is implemented by a reducer.

Let's create a simple React state with `useState`. The state contains a `count` number.

```react
type State = { count: number };

const [state, setState] = React.useState<State>({ count: 0 });
```

We can re-implement the same with `useReducer`.

```react
type Action = {
  type: 'COUNT_CHANGED',
  count: number,
};

const reducer: React.Reducer<State, Action> = (
  prevState: State,
  action: Action
): State => {
  switch (action.type) {
    case "COUNT_CHANGED":
      return { ...prevState, count: action.count };
  }
};

const [state, dispatch] = React.useReducer(reducer, { count: 0 });
```
Except for more lines of code, they function exactly the same.

Here `useReducer` takes in two parameters. 

- The first being a `reducer` function: `(prevState, action) => newState`. Upon `dispatch`ing an `action`, it updates (reduces) the prevState to a newState. 
- The second is the initial state, same to the one passed into `useState`. 

We have only one action called `COUNT_CHANGED`. So the following two lines will trigger the same state update:

```react
// with useState
setState({ count: 1 });

// with useReducer
dispatch({ type: 'COUNT_CHANGED', count: 1 });
```

# useState can replace useReducer, too

One claimed advantage of `useReducer` is its ability to handle complex state values. Let's create an example here. Let's say we have a root-level form component that contains three input components, and we want each input to handle its own value. The UI looks like below:

```react
    <UserForm>
      <FirstNameInput />
      <LastNameInput />
      <AgeInput />
    </UserForm>
```
We create a reducer below to handle 3 input values:

```react
// A complex state with user name and age
type UserState = {
  name: {
    first: string,
    last: string,
  },
  age: number,
};

// Three action types to update each state value
type Action =
  | {
      type: "FIRST_NAME_CHANGED";
      first: string;
    }
  | {
      type: "LAST_NAME_CHANGED";
      last: string;
    }
  | {
      type: "AGE_CHANGED";
      age: number;
    };


const reducer: React.Reducer<UserState, Action> = (
  prevState: UserState,
  action: Action
): UserState => {
  switch (action.type) {
    case "FIRST_NAME_CHANGED":
      return { ...prevState, name: { ...prevState.name, first: action.first } };
    case "LAST_NAME_CHANGED":
      return { ...prevState, name: { ...prevState.name, last: action.last } };
    case "AGE_CHANGED":
      return { ...prevState, age: action.age };
  }
};
```

And now use it in our `UserForm` component. Note that `dispatch` is passed into each `Input` so they can trigger actions to update their own field.

```react
const UserForm = () => {
  const [state, dispatch] = React.useReducer(reducer, {
    name: { first: "first", last: "last" },
    age: 40
  });
  return (
    <React.Fragment>
      <FirstNameInput value={state.name.first} dispatch={dispatch} />
      <LastNameInput value={state.name.last} dispatch={dispatch} />
      <AgeInput value={state.age} dispatch={dispatch} />
    </React.Fragment>
  )
}
```
Done. This is how `useReducer` can work for complex states. Now how to convert to `useState`?

A naive way is passing down one big state object to each `Input`. We have to pass down the entire state because each `Input` needs to know the 'full picture' of current state for it to properly construct a new state. Something like below:

```react
// This is a bad example.
const UserForm = () => {
  const [state, setState] = React.useState({
    name: { first: "first", last: "last" },
    age: 40
  });
  return (
    <React.Fragment>
      <FirstNameInput state={state} setState={setState} />
      <LastNameInput state={state} setState={setState} />
      <AgeInput state={state} setState={setState} />
    </React.Fragment>
  )
}
```

This is bad for several reasons:
1. No separation of duties: Each `Input` now requires full state as its props to work. Making it harder to refactor and unit test.
2. Poor performance: Any state change will trigger all `Input`s to re-render.

In fact, these are exactly the reasons why React team suggests using `useReducer` for this kind of complex state.

But that doesn't mean we can't use `useState` to achieve the same result. It just requires a bit more crafting.

```react
function Counter() {
  const { state, setFirstName, setLastName, setAge } = useComplexState({
    name: { first: "first", last: "last" },
    age: 40
  });
  return (
    <React.Fragment>
      <FirstNameInput value={state.name.first} setFirstName={setFirstName} />
      <LastNameInput value={state.name.last} setLastName={setLastName} />
      <AgeInput value={state.age} setAge={setAge} />
    </React.Fragment>
  )
}

// A custom hook that returns setter functions for each field.
// This is similar to what the reducer above is doing,
// we simply convert each action into its own setter function.
function useComplexState(initialState: UserState): any {
  const [state, setState] = React.useState<UserState>(initialState);
  const setFirstName = first =>
    setState(prevState => ({
      ...prevState,
      name: { ...prevState.name, first }
    }));
  const setLastName = last =>
    setState(prevState => ({
      ...prevState,
      name: { ...prevState.name, last }
    }));
  const setAge = age => setState(prevState => ({ ...prevState, age }));
  return { state, setFirstName, setLastName, setAge };
}
```
In fact, we can completely rewrite `useReducer` with only `useState`:
```react
const useReducerImplementedByUseState = (reducer, initialState) => {
  const [state, setState] = React.useState<State>(initialState);
  const dispatch = (action: Action) => setState(prevState => reducer(prevState, action));
  return [state, dispatch];
};

// above implementation
const [state, dispatch] = useReducerImplementedByUseState(reducer, initialState);
// is same with
const [state, dispatch] = useReducer(reducer, initialState);
```
In conclusion,
- For simple value state, do `useState` for it uses less lines.
- For complex state, use whichever you feel like at the moment 🤪

**Do you prefer `useState` or `useReducer` in your projects? Share your thoughts in the comment below ❤️**