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
CodePudding user response:
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
);
}
CodePudding user response:
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)