The Great Blog

3 Reasons to useReducer() over useState()

August 18, 2019

useReducer() is a method from the React Hooks API, similar to useState but gives you more control to manage the state. It takes a reducer function and initial state as arguments and returns the state and dispatch method:

const [state, dispatch] = React.useReducer(reducerFn, initialState, initFn);

A reducer (being called that because of the function type you would pass to an array methodArray.prototype.reduce(reducer, initialValue)) is a pattern taken from the Redux. If you are not familiar with Redux, in short, a reducer is a pure function that takes previous state and action as an argument, and returns the next state.

(prevState, action) => newState

Actions are a piece of information that describes what happened, and based on that information, the reducer specifies how the state should change. Actions are passed through the dispatch(action) method.

3 Reasons to Use It

Most of the time, you are well covered with just useState() method, which is built on top of useReducer(). But there cases when useReducer() is preferable.

Next state depends on the previous

It is always better to use this method when the state depends on the previous one. It will give you a more predictable state transition. The simple example would be:

function reducer(state, action) {
  switch (action.type) {
    case 'ADD': return { count: state.count + 1 };
    case 'SUB': return { count: state.count - 1 };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = React.useReducer(reducer, { count: 0 });
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'ADD'})}>Add</button>
      <button onClick={() => dispatch({type: 'SUB'})}>Substract</button>
    </>
  );
}

Complex state shape

When the state consists of more than primitive values, like nested object or arrays. For example:

const [state, dispatch] = React.useReducer(
  fetchUsersReducer,
  {
    users: [
      { name: 'John', subscribred: false },
      { name: 'Jane', subscribred: true },
    ],
    loading: false,
    error: false,
  },
);

It is easier to manage this local state, because the parameters depends from each other and the all the logic could be encapsulated into one reducer.

Easy to test

Reducers are pure functions, and this means they have no side effects and must return the same outcome given the same arguments. It is easier to test them because they do not depend on React. Let’s take a reducer from the counter example and test it with a mock state:

test("increments the count by one", () => {
  const newState = reducer({ count: 0 }, { type: "ADD" });
  expect(newState.count).toBe(1)
})

Conclusion

useReducer() is an alternative to useState() which gives you more control over the state management and can make testing easier. All the cases can be done with useState() method, so in conclusion, use the method that you are comfortable with, and it is easier to understand for you and colleagues.


Linas Spukas

Hi there! My name is Linas Spukas, I am a full stack web developer and this is my blog. About stuff and things... in development. Enjoy.