0

I'm trying to figure out how to update an array in an array in react nested state. I already learned about shallow copies but don't get how to implement it in this case.

There is a dispatch function with the useReducer hook to manage an array of objects called "Layer"

interface Layer {
  name: string,
  data: Item[],
  ...
}

every Layer holds an array of Items

class Item {
  id: number;
  ...
}

this is my dispatch function:

type ActionType =
  | { type: "ADD LAYER"; layer: Layer }
  | { type: "ADD ITEM"; item: Item; layer: Layer }
  | { type: "UPDATE ITEM"; item: Item; layer: Layer }
  | { type: "REMOVE ITEM"; item: Item; layer: Layer };

 const [layers, dispatch] = useReducer((state: Layer[], action: ActionType) => {
    switch (action.type) {
      case "ADD LAYER":
        {
          if (!state.includes(action.layer))
            state.push(action.layer);
          return state;
        }
      case "ADD ITEM":
        {
          if(!state.find(layer => layer.name === action.layer.name)?.data.find(item => item.id === action.item.id))
          state.find(layer => layer.name === action.layer.name)?.data.push(action.item)
          return state;
        }
      case "UPDATE ITEM": {
        //implementation missing
        return state;
      }
      case "REMOVE ITEM":
        {
          const newState = {
            ...state.map(layer =>
              layer.name == action.layer.name
                ? { ...layer, data: {
                  ...layer.data.filter(({ id }) => id !== action.item.id)
                } }
            : layer )
          };
          return newState;
        }
      default:
        throw new Error();
    }
  }, initialLayers);

look at the REMOVE ITEM case to see what I tried. But this approach make my application crash with the message: Uncaught TypeError: layers.map is not a function

case ADD LAYER and ADD ITEM are working

8
  • 1
    Could you post the full error, because the code in your question is not doing in any place layers.map only layer.map, so I would like to see where you map layers? Commented Aug 9, 2022 at 17:50
  • There are three error messages in my console: "Uncaught TypeError: layers.map is not a function" and two times "Uncaught TypeError: layers.find is not a function" Commented Aug 9, 2022 at 17:56
  • Chrome has a more detailed error message saying also " Warning: Cannot update a component (LayersProvider) while rendering a different component (Layer). To locate the bad setState() call inside Layer, follow the stack trace as described in reactjs.org/link/setstate-in-render" Commented Aug 9, 2022 at 18:05
  • Could you post the full message then, just trying to see the stack to have a better context. Commented Aug 9, 2022 at 18:14
  • So.... did you follow the stack trace as described at that link? Commented Aug 9, 2022 at 18:31

2 Answers 2

1

State is an array of objects:

case "REMOVE ITEM": {
  const newState = {};
  return newState;  // <- return an object
}

But this is returning an single object, so this will not work.

As you are using map() function, and it will create a new array, you might as well do this instead:

case "REMOVE ITEM": {
  return state.map((layer) =>
        layer.name == action.layer.name
          ? {
              ...layer,
              data: {
                ...layer.data.filter(({ id }) => id !== action.item.id),
              },
            }
          : layer
      );
}
Sign up to request clarification or add additional context in comments.

2 Comments

unfortunately it does not work yet. The error message changed to: Uncaught TypeError: _b.map is not a function
This should work
0

How about that?

case "REMOVE ITEM":
{
  let clonedState = JSON.parse(JSON.stringify(state))

  const layerIndex = clonedState.findIndex(layer => layer.name == action.layer.name);
  
  if (layerIndex > -1) {
    if (clonedState[layerIndex].data.filter(d => d.id == action.item.id).length > 0)
      clonedState[layerIndex].data = clonedState[layerIndex].data.filter(d => d.id !== action.item.id)
  }

  return clonedState;
}

NOTE: You can also modify data array like below (which is more elegant)

const itemIndex = clonedState[layerIndex].data.findIndex(d => d.id == action.item.id)
clonedState[layerIndex].data = [
  ...clonedState[layerIndex].data.slice(0, itemIndex),
  ...clonedState[layerIndex].data.slice(itemIndex + 1),
]

instead of

clonedState[layerIndex].data = clonedState[layerIndex].data.filter(d => d.id !== action.item.id)

Comments

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.