Answer a question

I have the add and Remove items state and it sends to the cart. Im my cart, when I add, it duplicates. Do I need to create a reducer for this increment so it can update from 1 to 2 instead of duplicating it?

const INITIAL_STATE = []

export const addItem = createAction('ADD_ITEM')
export const increment = createAction('INCREMENT')

export const removeItem = createAction('REMOVE_ITEM')

export default createReducer(INITIAL_STATE, {
  [addItem.type]: (state, action) => [...state, action.payload],
  [removeItem.type]: (state, action) => state.filter(item => item._id !== action.payload._id)
})

Here is my products:

const INITIAL_STATE = []

export const addProducts = createAction('ADD_PRODUCTS')
export const addProduct = createAction('ADD_PRODUCT')

export default createReducer(INITIAL_STATE, {
  [addProducts.type]: (state , action) => [...state, action.payload],
  [addProduct.type]: (state, action) => [...action.payload]
})

My reducer:

export default configureStore({
  reducer: {
    products: productsReducer,
    cart: cartReducer
  }
})

Answers

Quantity

Right now your cart state is saving an array of all of the items that are in the cart. There is no quantity property anywhere. One thing that you could do is add a quantity: 1 property to the item object when you add that item to the cart. But...

Normalizing State

You don't really need to save the while item object because you already have that information in products. All that you really need to know is the id of each item in the cart and its quantity. The most logical data structure for that is a dictionary object where the keys are the item ids and the values are the corresponding quantities.

Since you only need the id I would recommend that your action creators addItem, removeItem and increment should just be a function of the id instead of the whole item. This means that you would call them like dispatch(addItem(5)) and your action.payload would just be the id. The addProduct and addProducts actions still need the whole item object.

An array of products is fine and you can always call state.products.find(item => item._id === someId) to find a product by its id. But a keyed object is better! You want to have the keys be the item ids and the values be the corresponding objects. Redux Toolkit has built-in support for this structure with the createEntityAdapter helper.

Create Slice

There's not any problem with defining your actions though createAction, but it's not needed. You can replace createAction and createReducer with createSlice which combines the functionality of both.

Here's another way of writing your products:

import { createSlice, createEntityAdapter } from "@reduxjs/toolkit";

const productsAdapter = createEntityAdapter({
  selectId: (item) => item._id
});

const productsSlice = createSlice({
  // this becomes the prefix for the action names
  name: "products",
  // use the initial state from the adapter
  initialState: productsAdapter.getInitialState(),
  // your case reducers will create actions automatically
  reducers: {
    // you can just pass functions from the adapter
    addProduct: productsAdapter.addOne,
    addProducts: productsAdapter.addMany
    // you can also add delete and update actions easily
  }
});

export default productsSlice.reducer;
export const { addProduct, addProducts } = productsSlice.actions;

// the adapter also creates selectors
const productSelectors = productsAdapter.getSelectors(
  // you need to provide the location of the products relative to the root state
  (state) => state.products
);

// you can export the selectors individually
export const {
  selectById: selectProductById,
  selectAll: selectAllProducts
} = productSelectors;

And your cart:

import {createSlice, createSelector} from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: 'cart',
  // an empty dictionary object
  initialState: {},
  reducers: {
    addItem: (state, action) => {
      const id = action.payload;
      // add to the state with a quantity of 1
      state[id] = 1;
      // you might want to see if if already exists before adding
    },
    removeItem: (state, action) => {
      const id = action.payload;
      // you can just use the delete keyword to remove it from the draft
      delete state[id];
    },
    increment: (state, action) => {
      const id = action.payload;
      // if you KNOW that the item is already in the state then you can do this
      state[id]++;
      // but it's safer to do this
      // state[id] = (state[id] || 0) + 1
    }
  }
})

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

// you can select the data in any format
export const selectCartItems = createSelector(
  // only re-calculate when this value changes
  state => state.cart,
  // reformat into an an array of objects with propeties id and quantity
  (cart) => Object.entries(cart).map(([id, quantity]) => ({id, quantity}))
)

// select the quantity for a particular item by id
export const selectQuantityById = (state, id) => state.cart[id]

// you can combine the ids with the products, but 
// I actually recommend that you just return the ids and get the
// product data from a Product component like <Product id={5} quantity={2}/>
export const selectCartProducts = createSelector(
  // has two input selectors
  state => state.cart,
  state => state.products,
  // combine and reformat into an array of objects
  (cart, products) => Object.keys(cart).map(id => ({
    // all properties of the product
    ...products.entries[id],
    // added quantity property
    quantity: cart[id],
  }))
)
Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐