What exactly should you return from a redux slice reducer?

Viewed 1657

Today I had a bug where my components which were using useSelector wouldn't re-render when my redux state changed. After a quick read through the createSlice documentation I noticed that they were returning the state.something = action.payload, which I wasn't doing.

So in this code:

const personSlice = createSlice({
  name: "person",
  initialState,
  reducers: {
    setToken: (state, action) => {
      state.token = action.payload;
    },
    setUser: (state, action) => {
      state = { ...state, ...action.payload };
    },
  },
});

when I set the token, my app would update and it was working fine. But after dispatching the setUser action, my components which depended on the person state were not getting re-rendered. But then why did it work when I was setting the token without returning?

I changed my code to this:

const personSlice = createSlice({
  name: "person",
  initialState,
  reducers: {
    setToken: (state, action) => {
      state.token = action.payload;
    },
    setUser: (state, action) => {
      return state = { ...state, ...action.payload };
    },
  },
});

and everything works just fine, even without the return in setToken.

So I guess my ultimate question is, what exactly happens in a slice, why and what do I need to return from actions?

1 Answers

createSlice uses Immer inside. Immer works by either:

  • tracking mutations to an existing state object, like state.x = 123
  • letting you return a new value, like `return {...state, x: 123}

Assigning state = anything is not a mutation, and it doesn't return anything. It just reassigns the local variable named state to point to something else.

If you want to "merge in" some data, then yes, you need to either return {...state, ...someData}, or use Object.assign(state, someData) to "mutate" the existing state object.

Please see the Redux Toolkit "Writing Reducers with Immer" usage guide, which covers this topic.

Related