Search code examples
reactjsredux-toolkitimmer.js

Update some nested object of state in reducer


I have a state like this in a createSlice reducer. It is not stated anywhere in redux document examples, they are only about assignments of primitive properties. So, is it fine to assign action.payload to state.material. Any document references?

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

const initialState = {
  material: { option1: '', option2: '' }
};

const slice = createSlice({
  name: 'yourSliceName',
  initialState,
  reducers: {
    updateMaterial(state, action) {
      state.material = action.payload;
    }
  }
});

export const { updateMaterial } = slice.actions;
export default slice.reducer;


import { useDispatch } from 'react-redux';
import { updateMaterial } from './yourSlice';

const dispatch = useDispatch();

dispatch(updateMaterial({ option1: 'newOption1Value', option2: 'newOption2Value' }));

Edit

Redux Toolkit and Immer Redux Toolkit's createReducer API uses Immer internally automatically. So, it's already safe to "mutate" state inside of any case reducer function that is passed to createReducer:

It says to mutate state so we understand any kind of mutation to object, that's ok but not clarified with a single example.

Another example about nested data, does not update the nested value directly, but mutates the primitive again, as if did this on purpose.

Updating Nested Data

fixedTodoToggled(state, action) {
  const todo = state.find((todo) => todo.id === action.payload)
  if (todo) {
    // ✅ CORRECT: This object is still wrapped in a Proxy, so we can "mutate" it
    todo.completed = !todo.completed
  }
},

Even immer update patterns do not show assigning some deep object but they are about assignments of primitive properties, not objects.

In the examples, object mutation shows to add new object, but it does not show updating an object with another one, just assigning primitives.

Furthermore, in the immer pitfalls, caution against assigning external objects, which adds to the existing confusion. To ensure absolute clarity, seeking an official citation that explicitly clarifies the guidelines for mutating state for object valued properties within Redux Toolkit's createReducer API using Immer internally would be ideal.


Solution

  • Redux Toolkit comes with Immer, which allow you to do just this:

    Redux Toolkit and Immer

    Redux Toolkit's createReducer API uses Immer internally automatically. So, it's already safe to "mutate" state inside of any case reducer function that is passed to createReducer:

    const todosReducer = createReducer([], (builder) => {
     builder.addCase('todos/todoAdded', (state, action) => {
       // "mutate" the array by calling push()
       state.push(action.payload)
     })
    })
    

    In turn, createSlice uses createReducer inside, so it's also safe to "mutate" state there as well:

    const todosSlice = createSlice({
     name: 'todos',
     initialState: [],
     reducers: {
       todoAdded(state, action) {
         state.push(action.payload)
       },
     },
    })
    

    In short: yes, it's fine to assign action.payload to state.material.