简体   繁体   中英

Updating nested immutable state (redux)

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

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; ) } 

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM