Reputation: 250
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?
Upvotes: 2
Views: 18396
Reputation: 50639
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};
});
Upvotes: 7