DEV Community

Linas Spukas
Linas Spukas

Posted on

useReducer vs useState 3 Reasons to useReducer() over useState()

What It Is

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
);
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (14)

Collapse
 
clarity89 profile image
Alex K.

For the first point you can still use State's hook functional form: setState(state => state + 1).

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
neoprint3d profile image
Drew Ronsman

Explain

Collapse
 
joelmarkbyrd profile image
Joel Byrd

The first point says to use useReducer() when the next state depends on the previous one - he uses a counter as an example, where the next state is one more than the previous state. The point @clarity89 is making is that the useState() setter function provides a format that gives you access to the previous value in a way that gives you a predictable state transition, so useReducer() is not necessary in this case. You can read the documentation here, but basically the form is (using the counter example):

setState(prevCounterValue => prevCounterValue + 1)

So basically the first point is invalid, because useReducer() is not necessary to provide a predictable state transition when the next value depends on the previous value.

Collapse
 
aisone profile image
Aaron Gong

Why?

Collapse
 
kiszuriwalilibori profile image
Piotr Maksymiuk

What you mean?

Collapse
 
geisonmcd profile image
Geison • Edited

I have a screen where I show a list of (school) exams. When I click in one it navigates to another page where I can edit each exam field.

In this page I created a state for each single exam field (name, code, max weight, start date, end date, etc). Is this a case where I should've used a reducer? I also could create a state with the whole exam object right?

It's not clear to me the reducer advantage in my example.

Collapse
 
affkar profile image
Karthick

the component for the second page or the second component should just accept the exam as a prop.

Collapse
 
klmlab profile image
klm

Very Nice, Thank You

Collapse
 
mantenn profile image
Nazar Maksymchuk

not many benefits to reducer, I can still as easily test my state functions. by having the state handler be outside of the component.

Collapse
 
limpuls profile image
Laimis Petravicius

Always coming back to this when I need a reminder. Very well written!

Collapse
 
itscosmo profile image
Paul Kleimeyer

Very on point and well-written article. Thank you.

Collapse
 
mshez profile image
Muhammad Shahzad

Nice piece. userReducer is always recommended when state is bigger and complex.

Collapse
 
karthiganesan90 profile image
Karthikeyan Ganesan

Cool explanation, Thanks for the short crisp & clear note.