4

I have this slice:

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { IAuthState } from '../../types';

const initialState: IAuthState = {
  isAuthenticated: false,
  profile: null,
};

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setAuthState(state, { payload }: PayloadAction<IAuthState>) {
      console.log({ payload });
      state = payload;
    },
  },
});

export const { setAuthState } = authSlice.actions;
export const authReducer = authSlice.reducer;

That's the standard boilerplate slice.

The issue is that my state is not updated if I do it like this. Redux devtools says that there is no change. My payload is this:

{
    isAuthenticated: true,
    profile: {...} // big object
}

But if I do the assigning like this:

 reducers: {
    setAuthState(state, { payload }: PayloadAction<IAuthState>) {
      state.isAuthenticated = payload.isAuthenticated;
      state.profile = payload.profile
    },
  },

it updates the state. Why is that? And how can I batch update the whole state?

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Alex Ironside
  • 4,658
  • 11
  • 59
  • 119
  • 1
    [`return payload;`](https://redux-toolkit.js.org/api/createSlice#reducers)? – Emile Bergeron Jun 18 '21 at 15:28
  • Ok this works. But why didn't my try work, and why does this work? I thought that there is the whole immer thing that "catches" the mutation of state in the background. – Alex Ironside Jun 18 '21 at 15:30
  • 1
    `state` is a local identifier, reassigning it does nothing outside the function. Objects though, in JS, are passed "by reference" (not exactly, but close enough), so mutating the object inside the function does also mutate the same object anywhere it's referenced outside the function. – Emile Bergeron Jun 18 '21 at 15:32
  • Yes it does. Just not sure why returning updates the state. Is this what would happen if I did `state.profile=payload.profile`, anyway? – Alex Ironside Jun 18 '21 at 15:59
  • Redux Toolkit simplifies state updates by letting devs mutate the state object, which is usually prohibited with immutable states. Redux Toolkit achieves this by passing some proxy object which can be mutated without mutating the actual state. Returning a new object will just replace the whole state and ignore the proxy object. – Emile Bergeron Jun 18 '21 at 16:05
  • So to answer the question, no, it wouldn't be the same. Returning the profile would result in the new state being equal to the profile object `{ name: /*etc*/ }`, and doing `state.profile = profile` would mean that the state would now be `{ profile: { name: /*etc*/ } }`. So an additional nesting. – Emile Bergeron Jun 18 '21 at 16:12
  • It could work as well if that's what you'd like to use, depending on the responsibility of that slice! – Emile Bergeron Jun 18 '21 at 16:15
  • That makes sense, thanks. Would you like to make it into an answer and I can select it as best? – Alex Ironside Jun 20 '21 at 00:36
  • I misread part of your [last question](https://stackoverflow.com/questions/68037753/updating-a-state-in-a-slice-with-object-as-a-payload/68090559#comment120255363_68037753) and said that it wasn't the same, but after looking back at your code snippets, it would in fact be the same and my answer reflects this information as well. – Emile Bergeron Jun 22 '21 at 20:49

1 Answers1

13

How to update the whole state?

Regardless of if it's a slice using Redux Toolkit or a basic Redux reducer, if you'd like to replace the whole state, just return a whole new value.

setAuthState(state, { payload }: PayloadAction<IAuthState>) {
  return payload; // replaces the whole state
},

Why returning, instead of assigning, updates the state?

 state = payload;

state is a local identifier, reassigning it does nothing outside the function. Though in JavaScript, object references are passed by value, so mutating the state object inside the function does also mutate the same object anywhere it's referenced outside the function.

Redux Toolkit simplifies state updates by letting devs mutate the passed state object, which is usually prohibited with immutable states, like in vanilla Redux or with React state.

It uses the Immer library internally, which lets you write code that "mutates" some data, but actually applies the updates immutably. This makes it effectively impossible to accidentally mutate state in a reducer.

Returning a new object in a reducer will just replace the whole state and ignore the proxy object.

Is this what would happen if I did state.profile=payload.profile, anyway?

Yes, mutating each properties explicitly (taking advantage of RTK's usage of Immer) would do the same, with more code. Though if the payload ever has anymore properties, they wouldn't be included in the slice's state.

This could be what you want, no unwanted data ever gets to the state without explicitly adding it to the reducer. So it's not a bad thing to be explicit.

If you ever have lots of properties though, it could get annoyingly long to maintain.

setAuthState(state, { payload }) {
  state.isAuthenticated = payload.isAuthenticated;
  state.profile = payload.profile
  state.thing = payload.thing
  state.stuff = payload.stuff
  state.foo = payload.foo
  state.bar = payload.bar
  // etc...
},

What if I want the whole payload without replacing unrelated state values?

What you could do if the payload gets longer, but you were in a situation where you don't want to replace the whole state, is to return a new object from spreading the current state and the payload.

setAuthState(state, { payload }) {
  return {
     ...state,
     ...payload,
  };
},
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129