1

I've got state with a nested array that looks like the following:

{
    list: [
        {
        id: '3546f44b-457e-4f87-95f6-c6717830294b',
        title: 'First Nest',
        key: '0',
        children: [
            {
            id: '71f034ea-478b-4f33-9dad-3685dab09171',
            title: 'Second Nest',
            key: '0-0
            children: [
                {
                id: '11d338c6-f222-4701-98d0-3e3572009d8f',
                title: 'Q. Third Nest',
                key: '0-0-0',
                }
            ],
            }
        ],
    ],
    selectedItemKey: '0'
}

Where the goal of the nested array is to mimic a tree and the selectedItemKey/key is how to access the tree node quickly.

I wrote code to update the title of a nested item with the following logic:

let list = [...state.list];
let keyArr = state.selectedItemKey.split('-');
let idx = keyArr.shift();
let currItemArr = list;

while (keyArr.length > 0) {
  currItemArr = currItemArr[idx].children;
  idx = keyArr.shift();
}

currItemArr[idx] = {
  ...currItemArr[idx],
  title: action.payload
};

return {
  ...state,
  list
};

Things work properly for the first nested item, but for the second and third level nesting, I get the following Immer console errors

An immer producer returned a new value *and* modified its draft.
Either return a new value *or* modify the draft.

I feel like I'm messing up something pretty big here in regards to my nested array access/update logic, or in the way I'm trying to make a new copy of the state.list and modifying that. Please note the nested level is dynamic, and I do not know the depth of it prior to modifying it.

Thanks again in advance!

LeoVannini
  • 95
  • 3
  • 11

1 Answers1

2

Immer allows you to modify the existing draft state OR return a new state, but not both at once.

It looks like you are trying to return a new state, which is ok so long as there is no mutation. However you make a modification when you assign currItemArr[idx] = . This is a mutation because the elements of list and currItemArr are the same elements as in state.list. It is a "shallow copy".

But you don't need to worry about shallow copies and mutations because the easier approach is to just modify the draft state and not return anything.

You just need to find the correct object and set its title property. I came up with a shorter way to do that using array.reduce().

const keyArr = state.selectedItemKey.split("-");

const target = keyArr.reduce(
  (accumulator, idx) => accumulator.children[idx],
  { children: state.list }
);

target.title = action.payload;
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • 1
    Even better! Thanks for the clarity around my issue and for sharing an even simpler solution! Much thanks, Linda! – LeoVannini Mar 31 '21 at 21:00