React Mastery / Advanced React Hooks

useReducer

useReducer is a React hook used for advanced state management. It is an alternative to useState that is better suited for complex state logic, transitions involving multiple sub-values, or when the next state depends on the previous one.

It is heavily inspired by Redux architecture.


🧠 The Core Concept

The "Reducer" pattern works on the principle that instead of telling React what the new state is (as you do with useState), you tell React what happened (the Action), and a separate function (the Reducer) determines how to update the state.

The Four Pillars:

  1. State: The current data/source of truth.

  2. Action: A plain JavaScript object describing what happened (e.g., { type: 'increment' }).

  3. Reducer: A function that takes (state, action) and returns a newState.

  4. Dispatch: A function that sends the action to the reducer.

<aside>

🤝 This might feel overwhelming at first, but hang tight—you’ll understand everything gradually.

</aside>


👀 Syntax and Structure

const [state, dispatch] = useReducer(reducer, initialState);

Basic Implementation Example:

In above code, the actions are:

  • { type: 'increment' }

  • { type: 'decrement' }


❓ When to use useReducer vs useState

Feature

useState

useReducer

State Type

JavaScript primitives (string, number, boolean)

Objects or Arrays

Logic Complexity

Simple state updates

Complex logic (many state transitions)

Related State

Independent state pieces

State pieces that change together

Next State Logic

Defined in the component

Decoupled in a separate reducer function

Testing

Harder to test logic in isolation

Easy to test (reducer is a pure function)


✈️ Handling Payloads (Data inside Actions)

Most actions don't just signal a type; they also carry data. We call this the payload.

// Inside Reducer
case 'set_user_name':
  return { ...state, name: action.payload };

// Inside Component
dispatch({ type: 'set_user_name', payload: 'John Doe' });

💊 Best Practices & Pro Tips

  • Pure Functions: The reducer must be a pure function. It should not have side effects (no API calls, no random numbers, no console.log). Use useEffect for side effects.

  • Immutability: Never mutate state directly (e.g., state.count = 5). Always return a new object using the spread operator (...state).

  • Action Constants: In large apps, define action types as constants to avoid typos:

    const ACTION_TYPES = { INCREMENT: 'inc', DECREMENT: 'dec' };
  • Combine with Context: This is a "superpower" combination. You can pass the dispatch function down via Context, allowing any deep child component to update global state without prop drilling.