I tried changing a key's value inside my object, but it seems like I have been mutating the state.
The issue happens in state dispatch
Code:
export function nameList(id, rank, sub) {
const { nameItem: name } = store.getState().nameListSlice; //gets the nameItem state
if (!name.length || name.length === 0) {
const newName = [
{
name: "Male",
url: getNameUrl("Male", id, rank, sub),
icon: "Male",
toolTip: "Male",
active: false,
idName: "male",
},
{
name: "Female",
url: getNameUrl("Female", id, rank, sub),
icon: "Female",
toolTip: "Female",
active: false,
idName: "female",
},
];
store.dispatch(updateDetails(newName)); //The issue happen here
} else {
return name.map((n) => {
n.url = getNameUrl(n.name, id, rank, sub);
return n;
});
}
}
// This function returns a url
function getNameUrl(type, id, rank, sub) {
switch (type) {
case "Male": {
return `/male/${id}/${rank}/${sub}`;
}
case "Female": {
return `/female/${id}/${rank}/${sub}`;
}
}
}
Reducer/Action: (redux toolkit)
export const nameListSlice = createSlice({
name: 'nameItem',
initialState,
reducers: {
updateDetails: (state,action) => {
state.nameItem = action.payload
},
},
})
export const { updateDetails } = nameListSlice.actions
export default nameListSlice.reducer
The error I get:
TypeError: Cannot assign to read only property 'url' of object '#<Object>'
This happens in the above code disptach - store.dispatch(updateDetails(newName));
Commenting this code, fixes the issue.
How to dispatch without this error ?
Also based on this Uncaught TypeError: Cannot assign to read only property
But still same error
CodePudding user response:
RTK use immerjs underly, nameItem
state slice is not a mutable draft of immerjs when you get it by using store.getState().nameListSlice
. You can use the immutable update function createNextState to perform immutable update patterns.
Why you can mutate the state in case reducers are created by createSlice
like state.nameItem = action.payload;
?
createSlice
use createReducer
to create case reducers. See the comments for createReducer:
/**
* A utility function that allows defining a reducer as a mapping from action
* type to *case reducer* functions that handle these action types. The
* reducer's initial state is passed as the first argument.
*
* @remarks
* The body of every case reducer is implicitly wrapped with a call to
* `produce()` from the [immer](https://github.com/mweststrate/immer) library.
* This means that rather than returning a new state object, you can also
* mutate the passed-in state object directly; these mutations will then be
* automatically and efficiently translated into copies, giving you both
* convenience and immutability.
The body of every case reducer is implicitly wrapped with a call to
produce()
, that's why you can mutate the state inside case reducer function.
But if you get the state slice outside the case reducer, it's not a mutable draft state, so you can't mutate it directly like n.url = 'xxx'
.
E.g.
import { configureStore, createNextState, createSlice, isDraft } from '@reduxjs/toolkit';
const initialState: { nameItem: any[] } = { nameItem: [{ url: '' }] };
export const nameListSlice = createSlice({
name: 'nameItem',
initialState,
reducers: {
updateDetails: (state, action) => {
state.nameItem = action.payload;
},
},
});
const store = configureStore({
reducer: {
nameListSlice: nameListSlice.reducer,
},
});
const { nameItem: name } = store.getState().nameListSlice;
console.log('isDraft: ', isDraft(name));
const nextNames = createNextState(name, (draft) => {
draft.map((n) => {
n.url = `/male`;
return n;
});
});
console.log('nextNames: ', nextNames);
Output:
isDraft: false
nextNames: [ { url: '/male' } ]