Whichever approach you choose to use is perfectly acceptable and entirely up to you.
Modify state in place
setCustomEquipment: (state, action: PayloadAction<CustomEquipment[]>) => {
state.customEquipment = action.payload;
}
Creating a new state reference with shallow copy
setCustomEquipment: (state, action: PayloadAction<CustomEquipment[]>) => {
return {
...state,
customEquipment: action.payload,
};
}
When working with Redux-Toolkit slice reducers, you can either directly update the state or return a new state reference (shallow copy). For more information, check out Direct State Mutation.
To simplify things, createReducer
utilizes immer to allow you
to write reducers as if you were mutating the state directly. The reducer receives a proxy state that translates modifications into copy operations.
Writing "mutating" reducers makes the code cleaner, shorter, and helps reduce common mistakes when handling nested state. However, using Immer introduces some "magic" and has its own nuances. It's important to review the pitfalls mentioned in the immer documentation. The key point is to either modify the state
argument directly or return a new state, not both.
One of the pitfalls mentioned is:
Avoid reassigning the draft argument
Never reassign the draft argument (e.g., draft = myCoolNewState
).
Instead, make modifications to the draft or return a new state.
The common anti-patterns include:
Modifying draft state object and returning a new state reference
setCustomEquipment: (state, action) => {
state.otherPropery = "foo";
return {
...state,
property: action.payload,
};
}
Reassigning the current state draft object
setCustomEquipment: (state, action) => {
state = {
...state,
otherPropery: "foo",
property: action.payload,
};
}