How to Update Redux Reducers in React

This article provides a clear, step-by-step guide on how to update Redux reducers in a React application. You will learn the core rule of state immutability, how to update state using traditional Redux switch-case statements, and how to simplify the process using modern Redux Toolkit.

Understanding the Core Rule: Immutability

In Redux, reducers must be pure functions. This means they take the current state and an action as arguments, and return a brand-new state object. You must never mutate the existing state directly.

Instead of doing this:

// WRONG: Direct mutation
state.value = action.payload; 
return state;

You must do this:

// CORRECT: Returning a new object
return {
  ...state,
  value: action.payload
};

Method 1: Traditional Redux (Switch-Case)

In traditional Redux, you use the JavaScript spread operator (...) to copy existing state properties and override the specific values you want to update.

Here is an example of a counter reducer updating state using a switch-case statement:

const initialState = {
  count: 0,
  user: "Guest"
};

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state, // Copy existing state (keeps user as "Guest")
        count: state.count + 1 // Update only the count
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1
      };
    default:
      return state;
  }
}

Method 2: Modern Redux (Redux Toolkit)

The official and recommended way to write Redux today is with Redux Toolkit (RTK). Redux Toolkit uses a library called Immer under the hood.

Immer allows you to write “mutative” code (like state.value = 123) inside your reducers, but it automatically translates it into a safe, immutable update. This eliminates the need to use the spread operator for complex, nested state objects.

Here is how you write and update a reducer using Redux Toolkit’s createSlice:

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
    user: "Guest"
  },
  reducers: {
    increment: (state) => {
      // Immer allows you to "mutate" the state directly safely
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    updateUser: (state, action) => {
      state.user = action.payload;
    }
  }
});

// Export actions to be dispatched in React components
export const { increment, decrement, updateUser } = counterSlice.actions;

// Export the reducer to be added to the Redux store
export default counterSlice.reducer;

How to Dispatch the Updates in React

To trigger these reducer updates from your React components, use the useDispatch hook from the react-redux library.

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, updateUser } from './counterSlice';

function CounterComponent() {
  const count = useSelector((state) => state.counter.count);
  const user = useSelector((state) => state.counter.user);
  const dispatch = useDispatch();

  return (
    <div>
      <p>User: {user}</p>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(updateUser('John Doe'))}>Change User</button>
    </div>
  );
}