Search code examples
javascriptreactjsreduxredux-sagaredux-thunk

Dispatch not changing object in Redux state


So I'm building an ecommerce app, and I have two values stored in the redux state to handle the cart. One is an array of objects, each of which contains two keys for objectID and quantity. The other is an object containing product information with their objectID as a key. Here's the code for my action/dispatch.

addToCart: (product) => {
        return (dispatch, getState) => {
            const {productQuantity, storeSelected, products, storeKeys} = getState().Ecommerce;
            const UPC = product.UPC;
            let newQuant = 1;
            let newProducts;
            for(var p in productQuantity) {
                if (productQuantity[p].UPC === UPC) {
                    newProducts = products
                    newQuant += productQuantity[p].quantity;
                    productQuantity[p] = ({UPC, quantity: newQuant});
                }
            }
            if (!newProducts) {
                console.log("not found")
                productQuantity.push({UPC, quantity: 1});
                newProducts = {
                    ...products,
                    [UPC]: {
                        ...product,
                        price: product.stores[storeKeys[storeSelected]].price,
                        fromStore: storeKeys[storeSelected],
                    }
                }
            }
            dispatch({
                type: actions.CHANGE_CART,
                products: newProducts,
                productQuantity
            });
        };
    },

The action definitely runs. Next up is my reducer.

case actions.CHANGE_CART:
    console.log('This runs')
    console.log(action.products);
    console.log(action.productQuantity);
    return {
        ...state,
        products: action.products,
        productQuantity: action.productQuantity
    };

That also executes, and indeed when inspecting the state, productQuantity is updated in state but products is not. I've tried every configuration of definitions at this point and am tearing my hair out. Help would be much appreciated.

What I can confirm:

  • Correct data is being dispatched to the reducer.
  • The reducer executes.
  • ProductQuantity is updated by the reducer correctly and has the correct value.

Solution

  • The first issue is that you're mutating the existing state before dispatching the action, and mutating state is forbidden in Redux:

    const {productQuantity, storeSelected, products, storeKeys} = getState().Ecommerce;
                const UPC = product.UPC;
                let newQuant = 1;
                let newProducts;
                for(var p in productQuantity) {
                    if (productQuantity[p].UPC === UPC) {
                        newProducts = products
                        newQuant += productQuantity[p].quantity;
                        productQuantity[p] = ({UPC, quantity: newQuant});
                    }
                }
    

    Any changes to productQuantity here are mutating the existing data in the Redux store. Don't do that.

    I'm not quite clear on what's going on with products here, because the logic feels a bit confusing.

    My suggestions:

    First, start using our new official Redux Toolkit package. It has a configureStore() function that automatically sets up detection for accidental mutations, and will throw errors if you mutate.

    Second, try to move all this modification logic into your reducer, instead of doing it on the action creation side.

    Third, use Redux Toolkit's createSlice API, which uses the Immer library to to allow you to write "mutating" logic in your reducers and safely converts it into correct immutably-updated results.