How to Use useReducer Instead of Redux (When to Choose What)

April 17, 2019

Redux has been the standard for state management in React applications for years. However, with the introduction of React Hooks in 16.8, we now have a built-in alternative: useReducer.

The big question: Can useReducer replace Redux?

In this post, we’ll compare useReducer with Redux and go through a simple Todo app example to see when it makes sense to use useReducer instead of Redux.


useReducer vs. Redux: What’s the Difference?

At first glance, useReducer and Redux seem similar because both:

  • Use a reducer function to update state.
  • Follow an immutable state update pattern.
  • Use actions to describe state changes.

But there are key differences:

FeatureuseReducerRedux
Built-in?✅ Yes (React)❌ No (external library)
Boilerplate✅ Minimal❌ More setup required
Global State?❌ No (local state only)✅ Yes (via Redux store)
Middleware❌ Not supported✅ Supports middleware (e.g., Redux Thunk)
DevTools Support❌ No✅ Yes (Redux DevTools)
Best ForLocal, component-level stateComplex, global state management

When to Use useReducer
✔ Small to medium apps with simple state logic
✔ Component-level state that doesn’t need to be shared
✔ Avoiding extra dependencies (lighter bundle size)

When to Use Redux
✔ Large applications with deeply nested state
✔ State shared across multiple components
✔ Middleware requirements (async actions, logging, etc.)


Building a Simple Todo App with useReducer

To see useReducer in action, let’s build a simple Todo app where we can add and remove tasks.

Step 1: Define the Reducer Function

function todoReducer(state, action) {
  switch (action.type) {
    case "ADD":
      return [...state, { id: Date.now(), text: action.text }];
    case "REMOVE":
      return state.filter((todo) => todo.id !== action.id);
    default:
      return state;
  }
}

Step 2: Create the Todo App with useReducer

import React, { useReducer, useState } from "react";

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [text, setText] = useState("");

  const addTodo = () => {
    if (text.trim()) {
      dispatch({ type: "ADD", text });
      setText("");
    }
  };

  return (
    <div>
      <h2>Todo List</h2>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={addTodo}>Add Todo</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => dispatch({ type: "REMOVE", id: todo.id })}>
              Remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;

Why Use useReducer Here Instead of useState?

🔹 Keeps state logic centralized – Instead of multiple useState calls, all updates go through a single reducer.
🔹 Easier to scale – If we add more actions (like marking tasks as complete), we only modify the reducer.
🔹 Follows Redux-like patterns – Helps in structuring state updates predictably.


Conclusion: useReducer or Redux?

React’s useReducer provides a lightweight alternative to Redux for handling local component state. It’s perfect for medium-sized applications where Redux might feel like overkill.

🚀 Rule of thumb:

  • Use useReducer when state is local to a component.
  • Use Redux when state needs to be shared across multiple components.

If your app grows and requires global state, you can still combine useReducer with React Context API or move to Redux when needed.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *