Reputation: 98
I'm looking to add a new item object to a category in a reducer. The reducer receives a category index and a new item object.
Does anyone know the best way to update the state immutably with this data structure:
const initialState = {
categories: [
{
id: 1,
name: "vegetables",
items: [
{name: "potatoes", id: Math.floor(Math.random() * 99999)},
{name: "carrots", id: Math.floor(Math.random() * 99999)}
]
},
{
id: 2,
name: "dairy",
items: [
{name: "milk", id: Math.floor(Math.random() * 99999)},
{name: "cheese", id: Math.floor(Math.random() * 99999)}
]
},
{
id: 3,
name: "meat",
items: [
{name: "chicken", id: Math.floor(Math.random() * 99999)}
]
}
]
}
Or is it best to use an external package, such as immutable.js?
There are many other similar questions on stackoverflow but none that have the same structure.
Update
The rest of the reducer looks like:
const shoppingItemsReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_SHOPPING_ITEM:
const categories = [...state.categories];
categories[action.selectedCategoryIndex].items.push(action.newItemObj);
return {
...state,
categories
}
default:
return state
}
}
Using push
works fine but it's mutating the state
Upvotes: 1
Views: 996
Reputation: 814
You can do the following without using push
const initialState = {
categories: [
{
id: 1,
name: "vegetables",
items: [
{name: "potatoes", id: Math.floor(Math.random() * 99999)},
{name: "carrots", id: Math.floor(Math.random() * 99999)}
]
},
{
id: 2,
name: "dairy",
items: [
{name: "milk", id: Math.floor(Math.random() * 99999)},
{name: "cheese", id: Math.floor(Math.random() * 99999)}
]
},
{
id: 3,
name: "meat",
items: [
{name: "chicken", id: Math.floor(Math.random() * 99999)}
]
}
]
}
const categoryId = 2; // categoy want to update
cosnt newItem = {name: "Butter", id: Math.floor(Math.random() * 99999)}
const newState = {
...initialState, // or state
categories: initialState.categories.map(category => {
if(category.id === categoryId) {
return {
...category,
items: [
...category.items,
newItem
]
}
}
return category;
)
}
Upvotes: 2
Reputation: 2395
Variables containing primitive types will always point to the actual value. So if you pass it to another variable, the other variable will get a fresh copy of that value.
However, Objects and Arrays are always passed by reference. So if you were to pass an Object or Array to another variable, they will both refer to the same original object. If you were to modify any of those variables referencing the original, it would also modify the original Object/Array.
To circumvent this, you'd have to create a new copy of the Array. You can do this in plain javascript like so:
const initialState = [
{
id: 1,
name: "category 1",
items: [
{name: "item 1", id: 1},
{name: "item 2", id: 2}
]
},
{
id: 2,
name: "category 2",
items: [
{name: "item 3", id: 3},
{name: "item 4", id: 4}
]
},
{
id: 3,
name: "category 3",
items: [
{name: "item 5", id: 5},
{name: "item 6", id: 6}
]
}
]
const newState = [...initialState, newDataObject]
newState is a newly created array with copies of initialState
with newDataObject
pushed to the last index of the newState
array.
EDIT: I saw you updated your question with your redux reducer. You're currently returning an object that references the initialstate:
return {
...state,
categories
}
It should instead be returning a new object with catagories pushed up to it. You can use es6's Object.assign()
to merge two object, and it will return a completely new Object containing both.
Change your return statement to:
return Object.assign({}, state, categories)
Upvotes: 0