Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 8 of the Intermediate React: Hooks, State & Data Patterns course
ReactJune 25, 20263 min read

Complex State with useReducer: A React Developer's Guide

Master useReducer to manage complex state transitions in React. Learn how to write a reducer function, dispatch actions, and clean up your component logic.

ReactuseReducerstate managementhooksJavaScriptfrontend

Previously in this course, we explored how to build custom hooks in Introduction to Custom Hooks to encapsulate logic. While custom hooks are excellent for sharing behavior, they don't always solve the problem of managing "spaghetti" state—where multiple useState calls lead to interdependent updates that are hard to track.

When your dashboard's state logic grows beyond simple toggles, useReducer becomes your most powerful tool. It allows you to move state transition logic out of your components and into a predictable, testable function.

Why useReducer?

In smaller components, useState is perfectly fine. However, as our dashboard project grows, we often face scenarios where:

  1. One user action (like clicking "Sync Data") triggers multiple state changes (e.g., setLoading(true), setError(null), setData(null)).
  2. The next state depends heavily on the previous state.
  3. The business logic for updating state is becoming too complex for an event handler.

useReducer solves this by forcing you to define all possible state transitions in one place: a reducer function. This pattern is foundational for React state management: Reducers vs. State Machines and keeps your components focused on rendering rather than logic orchestration.

The Anatomy of useReducer

To use useReducer, you need three things:

  • Initial State: The starting shape of your data.
  • Reducer Function: A pure function that takes the currentState and an action, then returns the nextState.
  • Dispatch: A function provided by the hook to trigger state updates.

Worked Example: A Dashboard Data Fetcher

Let's refactor our dashboard's data fetching logic. Instead of managing isLoading, data, and error as three separate useState variables, we'll group them into one object.

JAVASCRIPT
import { useReducer } from CE9178">'react';

// 1. Define the initial state
const initialState = {
  data: null,
  loading: false,
  error: null,
};

// 2. The reducer function: Pure logic for transitions
function dashboardReducer(state, action) {
  switch (action.type) {
    case CE9178">'FETCH_START':
      return { ...state, loading: true, error: null };
    case CE9178">'FETCH_SUCCESS':
      return { ...state, loading: false, data: action.payload };
    case CE9178">'FETCH_ERROR':
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
}

function Dashboard() {
  const [state, dispatch] = useReducer(dashboardReducer, initialState);

  const fetchData = async () => {
    dispatch({ type: CE9178">'FETCH_START' });
    try {
      const response = await fetch(CE9178">'/api/dashboard');
      const data = await response.json();
      dispatch({ type: CE9178">'FETCH_SUCCESS', payload: data });
    } catch (err) {
      dispatch({ type: CE9178">'FETCH_ERROR', payload: err.message });
    }
  };

  return (
    <div>
      {state.loading && <p>Loading...</p>}
      {state.error && <p>Error: {state.error}</p>}
      {state.data && <pre>{JSON.stringify(state.data, null, 2)}</pre>}
      <button onClick={fetchData}>Refresh Data</button>
    </div>
  );
}

Notice how dispatch acts as an event emitter. The component no longer cares how the state is updated; it only broadcasts what happened. This separation is the key to maintaining a clean Review of State Management: Choosing the Right React Strategy as your app scales.

Hands-on Exercise

In your dashboard project, locate the component handling the user's "filter" settings (e.g., date range, category selection, and search term).

  1. Create an initialState object with these three fields.
  2. Implement a filterReducer that handles updates for each field.
  3. Replace your individual useState hooks with useReducer.
  4. Ensure that dispatching an action for SET_SEARCH_TERM doesn't accidentally wipe out your dateRange state.

Common Pitfalls

  • Mutating State: Never mutate the state object directly in your reducer. Always return a new object (e.g., using the spread operator { ...state }).
  • Side Effects in Reducers: Reducers must be pure. Do not perform API calls or generate random numbers inside the reducer function. Keep side effects in your event handlers or useEffect.
  • Over-Engineering: Don't use useReducer for simple counters or toggles. It adds boilerplate. If you only have one piece of state, useState is almost always the correct choice.

Recap

By centralizing your logic, useReducer makes complex state transitions predictable. You define actions to describe intent and a reducer to handle the state change. This pattern prevents "impossible states"—like having both loading and error be true—because you control the transition logic explicitly.

Up next: We will dive deeper into managing object-based state, focusing on how to handle deeply nested data structures immutably.

Previous lessonRefactoring Dashboard SettingsNext lesson Managing Object-Based State
Back to Blog

Similar Posts

ReactJune 25, 20263 min read

Cleanup Functions in useEffect: Preventing Memory Leaks

Master cleanup functions in useEffect to handle component unmounting and cancel pending API requests, ensuring your React app stays stable and bug-free.

Read more
ReactJune 25, 20263 min read

Handling Loading States in React: Improving UX and Performance

Learn to master loading state in React to improve UX. Discover how to conditionally render spinners while your API fetches data for your movie app.

Part of the course

Intermediate React: Hooks, State & Data Patterns

intermediate · Lesson 8 of 48

  1. 1

    Mastering useRef for DOM Access

    4 min
  2. 2

    Persistent Mutable Values with useRef

    4 min
  3. 3

    Memoizing Expensive Calculations with useMemo

    3 min
Read more
ReactJune 25, 20263 min read

Architecting Global State with Context and Reducer

Master professional state management by combining Context API and useReducer. Learn how to build a centralized, scalable store for your React dashboard.

Read more
4

Optimizing Function References with useCallback

3 min
  • 5

    Introduction to Custom Hooks

    4 min
  • 6

    Building a useLocalStorage Hook

    4 min
  • 7

    Refactoring Dashboard Settings

    4 min
  • 8

    Complex State with useReducer

    3 min
  • 9

    Managing Object-Based State

    3 min
  • 10

    Introduction to Context API

    3 min
  • 11

    Architecting Global State with Context and Reducer

    3 min
  • 12

    Implementing Theme Context

    4 min
  • 13

    Structuring State for Performance

    Coming soon
  • 14

    Handling Authentication State

    Coming soon
  • 15

    Integrating Reducers with Auth State

    Coming soon
  • 16

    Introduction to React Router

    Coming soon
  • 17

    Dynamic Routing with URL Parameters

    Coming soon
  • 18

    Nested Routes and Layouts

    Coming soon
  • 19

    Protected Routes for Authenticated Views

    Coming soon
  • 20

    Programmatic Navigation

    Coming soon
  • 21

    Building the Dashboard Navigation Structure

    Coming soon
  • 22

    Asynchronous Data Lifecycle

    Coming soon
  • 23

    Caching Strategies with React Query

    Coming soon
  • 24

    Mutations and Data Updates

    Coming soon
  • 25

    Synchronizing Client and Server State

    Coming soon
  • 26

    Integrating Live Data into the Dashboard

    Coming soon
  • 27

    Error Handling and Loading UI

    Coming soon
  • 28

    Controlled vs Uncontrolled Components

    Coming soon
  • 29

    Real-time Form Validation

    Coming soon
  • 30

    Schema-based Validation with Zod

    Coming soon
  • 31

    Handling Multi-step Forms

    Coming soon
  • 32

    Optimizing Form Submissions

    Coming soon
  • 33

    Performance Profiling with React DevTools

    Coming soon
  • 34

    Refactoring for Scalability

    Coming soon
  • 35

    Finalizing Dashboard Data Flow

    Coming soon
  • 36

    Deploying the Application

    Coming soon
  • 37

    Advanced Hook Composition

    Coming soon
  • 38

    Implementing Middleware for State

    Coming soon
  • 39

    Advanced Context Patterns

    Coming soon
  • 40

    Router Loaders and Data Prefetching

    Coming soon
  • 41

    Complex Route Guards

    Coming soon
  • 42

    Handling Large Datasets in UI

    Coming soon
  • 43

    Testing Hooks and Components

    Coming soon
  • 44

    Managing Global Modals

    Coming soon
  • 45

    Implementing Keyboard Shortcuts

    Coming soon
  • 46

    Optimizing Asset Loading

    Coming soon
  • 47

    Internationalization Basics

    Coming soon
  • 48

    Managing WebSocket Connections

    Coming soon
  • View full course