Skip to content

React Hooks: Mastering useReducer (Casual Guide)

Posted on:January 21, 2026 at 09:00 AM

Hey everyone! 👋

We’ve all been there with useState. It’s simple, it’s clean, and it gets the job done for most things. But then… your component grows.

Suddenly you have five different useState hooks, and updating one requires checking three others. Your handleUpdate function looks like a bowl of spaghetti. 🍝

Enter useReducer.

It sounds scary because it reminds people of Redux (which can be intimidating), but trust me, useReducer is actually quite friendly once you get to know it.

Let’s break it down.

What is useReducer?

Think of useState like a light switch. You flip it on, you flip it off. Simple direct control.

useReducer is more like an Instruction Manual for your state. Instead of just saying “Change value to X”, you say “Hey React, apply the ‘INCREMENT’ instruction”.

It allows you to move the logic of how state changes OUTSIDE of your component.


1. The Setup

To use it, you need three things:

  1. The State: Your data (e.g., { count: 0 }).
  2. The Action: A message describing what happened (e.g., { type: 'INCREMENT' }).
  3. The Reducer: A function that takes current state and an action, and returns the NEW state.

Let’s look at the Reducer function first.

// This function doesn't need to live inside the component!
const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    case "RESET":
      return { count: 0 };
    default:
      return state;
  }
};

See? It’s just a pure JavaScript function. No React magic inside. It just takes (state, action) and returns newState.


2. Using the Hook

Now, let’s wire it up in our component.

import { useReducer } from "react";

// reuse the reducer function from above
// const reducer = ...

const Counter = () => {
  // 1. Initialize the hook
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      {/* 2. Dispatch actions instead of setting state directly */}
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
      <button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
    </div>
  );
};

Notice we don’t say setCount(count + 1). We say dispatch({ type: "INCREMENT" }). We are sending a message. The reducer handles the logic.


Why bother? This looks like more code!

You’re right. For a simple counter, useState is better. But useReducer shines when:

  1. State is complex: You have an object with deeply nested values.
  2. Updates depend on other state: e.g., “When ‘Status’ changes to ‘Success’, also clear ‘Error’ and set ‘Loading’ to false”.
  3. Testing: You can test the reducer function in isolation without rendering the component!

Real World Scenario: A Form

Imagine a form with name, email, loading, and error. With useState, you might have 4 hooks. Or one big object that you have to spread spread spread ...state every time.

With useReducer:

// One update handles multiple changes cleanly
case "LOGIN_SUCCESS":
  return {
    ...state,
    loading: false,
    user: action.payload,
    error: null
  };

It keeps your event handlers clean. Your handleSubmit just dispatches one action, and the reducer handles the mess.


Closing

Don’t be afraid of useReducer. You don’t have to use it everywhere.

It’s a powerful tool in your React utility belt. 🛠️

Happy Coding! 🚀