Home > Software engineering >  Cannot assign to read only property 'url' of object '#<Object>'
Cannot assign to read only property 'url' of object '#<Object>'

Time:09-14

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' } ]
  • Related