You don't need useReducer
Tags: react, hooks, javascript, frontend
Published: 2019-11-13 09:15:36

There's a misconception these days in the React community that whenever you want to manage a complex object instead of breaking down to individual state variables, you should use `useReducer`.

But, as I'll show in this article, managing a complex object and the changes it can go through can be more easily done using `useState`.

##Show me the code
OK, here are the 2 versions, which are pretty much equivalent in result:

**useReducer:**

```javascript
function reducer(state, action) {
  switch(action.type) {
    case 'MOVE_RIGHT':
      return { ...state, left: state.left + action.step };
    case 'MOVE_DOWN':
      return { ...state, top: state.top + action.step };
    default:
      return state;
  }
}
const [position, dispatch] = useReducer(reducer, { left: 0, top: 0 });

dispatch({ type: 'MOVE_RIGHT', step: 10 });
```

**useState:**

```javascript
const [position, setPosition] = useState({ left: 0, top: 0 });
const actions = useMemo(() => ({
  moveRight: step => {
    setPosition(state => ({ ...state, left: state.left + step }))
  },
  moveDown: step => {
    setPosition(state => ({ ...state, top: state.top + step }))
  }
}), []);

actions.moveRight(10);
```

> Note the `useMemo`. The reason it is there is to make sure we gain feature parity with `useReducer`. Since the `dispatch` parameter of `useReducer` is always constant throughout renders, I wanted the actions to be that as well

## So what did we gain?
1. Easier TypeScript support.
Inside the reducer, you are dealing with different types of actions, each with its own parameters. To get that working well with TypeScript you'll need to add a type per action and follow some rules to make sure TypeScript can differentiate between the different action types inside the switch case.
It's less intuitive and more verbose than working with plain function signatures, which have the parameter types colocated next to the implementation.

2. Better IDE Support
If you use functions and not action objects, you can jump to its definition, look for references to a function, and rename it globally with the help of the IDE.

3. Less error-prone
String action types are prone to undetected errors. You can obviously bypass this with some constants or TypeScript magic, but that means you need to add more boilerplate code.

## Did we lose anything?
It's easier to test a reducer. Or is it?
Since a reducer is a pure function, it's easier to test. Testing a function that uses the setter function will require some extra wiring.

But, we could easily write a test-util once that will help us test object actions, and reuse it where-ever we need. Moreover, the benefits of functions over action objects will also prove useful inside tests, such as better TS and IDE support.

So all in all, I would argue that the benefits surpass the drawbacks in terms of testing.

## What about reducers in Redux?
For a global state manager, there are other benefits to using action objects. Several reducers can handle the same action, you get a nice view of the history of the app state using the devtools, and it's easy to export and import entire user flows. You can think of every state mutation as a user intent that is expressed using an object - and it opens up more possibilities.

But for a local state, these benefits do not exist. You always handle the action in the same reducer, and the action history of a single component is not that interesting.

## Change my mind
I know it's a very common pattern in the industry to use a `useReducer`, but I really hate doing things just because they're popular if they don't make any sense to me.
So I'm very very very much open to other opinions and ideas, and if you can change my mind I'd love to see how.