skip to Main Content

Right now I can reflect changes of total price if quantity changes. here is an example what I am doing now:

price of every single item 
quantity {2} {200}
quantity {2} {100}
    
total: price = 600

Here is my cart slice:

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    total: 0,
  },
  reducers: {
    addItem(state, action) {
      const { id, title, price, size, color, image, quantity = 1 } = action.payload;
      const existingItem = state.items.find(item => item.id === id);
      if (existingItem) {
        existingItem.quantity += quantity;
      } else {
        state.items.push({ id, title, price, size, color, image, quantity });
      }
      state.total += price * quantity;
      
    },
    removeItem(state, action) {
      const { id, quantity = state.quantity } = action.payload;
      const existingItem = state.items.find(item => item.id === id);
      if (existingItem) {
        if (existingItem.quantity > quantity) {
          existingItem.quantity -= quantity;
        } else {
          state.items = state.items.filter(item => item.id !== id);
        }
        state.total -= existingItem.price * quantity;
      }
    },
    removeSingleItem(state, action) {
      const { id, quantity = 1 } = action.payload;
      const existingItem = state.items.find(item => item.id === id);
      if (existingItem) {
        if (existingItem.quantity > quantity) {
          existingItem.quantity -= quantity;
        } else {
          state.items = state.items.filter(item => item.id !== id);
        }
        state.total -= existingItem.price * quantity;
      }
    },
  },
});

export const {
  addItem,
  removeItem,
  addSingleItem,
  removeSingleItem
} = cartSlice.actions;
export default cartSlice.reducer;

My expected result will be:

price of every single item 
quantity {2} {400}
quantity {2} {200}
    
total: price = 600

2

Answers


  1. You don’t need a total in state. You can derive that from the items array. Read more about defining a minimal representation of state here.

    So you should remove any mention of total in your slice.

    After that, once you update the items array with quantity and price, you can use a selector to get the total from that.

    import { useSelector } from "react-redux";
    
    const total = useSelector((state) => state.cart.items.reduce((a, b) => a + b.quantity, 0))
    

    Here we’re using a selector which takes state as an argument, which allows us to access the cart slice. From the cart slice we access the items array and use the reduce array method to derive the total quantity.

    Login or Signup to reply.
  2. React state should be the minimum data it takes to represent the "state"; it’s considered a bit of a React anti-pattern to store derived state in state. In this case a cart total can be computed directly from the state.cart.items array.

    Example:

    const items = useSelector(state => state.cart.items);
    
    const total = items.reduce((total, { price = 0, quantity = 0 }) => {
      return total + price * quantity;
    }, 0);
    

    If you want or need to you can memoize the total using the useMemo hook.

    const items = useSelector(state => state.cart.items);
    
    const total = useMemo(() => {
      return items.reduce((total, { price = 0, quantity = 0 }) => {
        return total + price * quantity;
      }, 0);
    }, [items]);
    

    redux-toolkit also exports reselect functions. You could use createSelector to create a memoized selector function. In the cartSlice file create an input selector to select the state.cart.items array and then create the memoized selector`.

    import { createSlice, createSelector } from '@reduxjs/toolkit';
    
    const cartSlice = createSlice({
      ...
    });
    
    export const cartItemsSelector = state => state.cart.items;
    
    export const cartTotalSelector = createSelector(
      [cartItemsSelector],
      items => items.reduce((total, { price = 0, quantity = 0 }) => {
        return total + price * quantity;
      }, 0)
    );
    
    const items = useSelector(cartItemsSelector);
    const total = useSelector(cartTotalSelector);
    
    ...
    

    Remove the total state and logic from the cart slice.

    const cartSlice = createSlice({
      name: 'cart',
      initialState: {
        items: [],
      },
      reducers: {
        addItem(state, action) {
          const { id } = action.payload;
          const existingItem = state.items.find(item => item.id === id);
    
          if (existingItem) {
            existingItem.quantity += quantity;
          } else {
            state.items.push({ ...action.payload, quantity: 1 });
          }
        },
        removeItem(state, action) {
          const { id, quantity = 1 } = action.payload;
          const existingItem = state.items.find(item => item.id === id);
    
          if (existingItem) {
            if (existingItem.quantity > quantity) {
              existingItem.quantity -= quantity;
            } else {
              state.items = state.items.filter(item => item.id !== id);
            }
          }
        },
        removeSingleItem(state, action) {
          const { id, quantity = 1 } = action.payload;
          const existingItem = state.items.find(item => item.id === id);
          if (existingItem) {
            if (existingItem.quantity > quantity) {
              existingItem.quantity -= quantity;
            } else {
              state.items = state.items.filter(item => item.id !== id);
            }
          }
        },
      },
    });
    
    export const {
      addItem,
      removeItem,
      addSingleItem,
      removeSingleItem
    } = cartSlice.actions;
    
    export default cartSlice.reducer;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search