2

I have this problem.

With interface:

export interface IDevice {
  id: string,
  selected: boolean
}

creating instance by:

let newDevice: IDevice = {
  id: uuid4(),
  selected: false,
} as IDevice;

It gets added to array in recoil state, and in React used in a function where array has been retrieved with useRecoilState(). const [leftList, setLeftList] = React.useState<IDevice[]>([]);

Now it is being used in a handler for selecting the devices on a list control, here the error occurs:

...
leftList.map((item: IDevice) => {     
      if (item.id === event.dataItem.id) {
        item.selected = !item.selected;
      }
      return item;
    })
...

And I get the error: Cannot assign to read only property 'selected' of object '#'

Even cloning the array first by [...leftList] does not help.

I'm lost:-) Hope someone can spread light on this?

1
  • 1
    State shouldn't be modified directly, which is what TS is trying to tell you by making your object read-only, create a new object and return that instead return {...item, selected: item.id === event.dataItem.id ? !item.selected : item.selected}. Cloning the array using the spread syntax will only do a shallow copy, and won't do a deep-copy of the objects within it Commented Feb 9, 2021 at 9:59

1 Answer 1

7

State shouldn't be modified directly, which is what you're currently doing now in your .map() method by updating the objects. TS is trying to tell you not to do this by making your objects read-only.

Instead, you can create a new object with all the properties from item (done using the spread syntax ...), along with a new overwritting property selected, which will use the negated version of the item's currently selected item if the id matches the event's data item id, or it'll keep the original selected value:

leftList.map((item: IDevice) => ({
  ...item, 
  selected: item.id === event.dataItem.id ? !item.selected : item.selected
}))

Cloning the array using the spread syntax ([...leftList]) will only do a shallow copy, and won't do a deep-copy of the objects within it, as a result, modifying the object references within .map() is still modifying the original state.

Or, instead of spreading the item object and creating a new object each time (which can hinder performance a little as pointed out by @3limin4t0r), you can instead only return a newly created object when you want to modify the selected property:

leftList.map((item: IDevice) => {     
  if (item.id !== event.dataItem.id) return item;
  return {...item, selected: !item.selected};
});
Sign up to request clarification or add additional context in comments.

1 Comment

Note that this solution isn't the best for performance since you create a new object for each element within the array, even if that element does not change. You could add a guard clause to resolve this if (item.id !== event.dataItem.id) return item then create the copy with changes below that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.