Redux Reducers and State Management

Welcome to the tutorial on Redux Reducers. Think of reducers as the chefs in a kitchen. While actions are the customer orders, reducers take those orders and decide what dish to prepare. They handle the logic of transforming the current state into the next state, based on the action they receive.

What is a Reducer?

A reducer is a function that receives the current state and an action, determines how to update the state based on the action type, and then returns the next state. Reducers are pure functions and must avoid side effects or state mutation.

Analogy: Imagine the state as your kitchen inventory. When an order (action) comes in, the chef (reducer) checks what needs to change and returns a new updated inventory. But the chef never scribbles over the old inventory list; instead, they make a fresh copy with updates.

Using a Switch Statement in a Reducer

Here’s a basic example of a reducer that handles fruits in inventory:

const fruitReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_FRUIT':
      return [...state, action.fruit];
    default:
      return state;
  }
};

When the Redux store initializes, it calls the reducer with an undefined state. The reducer sets its initial state to an empty array via the default parameter state = [].

If an action with type: 'ADD_FRUIT' is received, the reducer returns a new array with the new fruit appended. Otherwise, it returns the original state unchanged.

Expanding the Reducer with More Actions

Let’s add more functionality. We can handle:

const fruitReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_FRUIT':
      return [...state, action.fruit];
    case 'ADD_FRUITS':
      return [...state, ...action.fruits];
    case 'SELL_FRUIT': {
      const index = state.indexOf(action.fruit);
      if (index !== -1) {
        const newState = [...state];
        newState.splice(index, 1);
        return newState;
      }
      return state;
    }
    case 'SELL_OUT':
      return [];
    default:
      return state;
  }
};

Note: We use [...state] to create a copy of the array. This is crucial because splice() modifies the array in place. By copying first, we avoid mutating the original state.

Avoiding State Mutation

Reducers must never mutate their arguments. Always return a new object or array. Mutating the state can lead to unpredictable behavior and bugs.

Bad Reducer (mutates state):

const badReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT_COUNTER':
      state.count++;
      return state;
    default:
      return state;
  }
};

Good Reducer (copies state):

const goodReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT_COUNTER': {
      const nextState = Object.assign({}, state);
      nextState.count++;
      return nextState;
    }
    default:
      return state;
  }
};

Splitting Reducers for Complex State

As your app grows, managing everything in one reducer becomes messy. Just like hiring more chefs for different kitchen stations, split your reducer to manage slices of the state.

Example State:

{
  fruit: ['APPLE', 'ORANGE'],
  farmers: {
    1: { id: 1, name: 'John Smith', paid: false },
    2: { id: 2, name: 'Sally Jones', paid: false }
  }
}

We need two reducers:

Combining Reducers

Redux’s combineReducers helps you stitch multiple reducers into one root reducer.

import { combineReducers, createStore } from 'redux';

const rootReducer = combineReducers({
  fruit: fruitReducer,
  farmers: farmersReducer
});

const store = createStore(rootReducer);
export default store;

Analogy: Think of combineReducers as assembling different departments in a company (fruit and farmers) under one management system (store).

Follow-Along Exercise

  1. Create fruitReducer.js and farmersReducer.js in src/reducers.
  2. Define the reducers to manage their respective state slices.
  3. Create a store.js file in src/store and use combineReducers and createStore.
  4. Test dispatching different actions to see state changes.

Why Reducers Matter

Real-World Example

In an e-commerce app, you may have:

Further Exploration

Summary

Reducers are the core logic handlers of Redux. You learned how to:

Resources

Learn more at the official Redux documentation: Redux Documentation