¿Por qué recibo una alerta de mutación de estado cuando hago una copia del objeto con el operador de propagación? Estoy tratando de hacer un carrito de compras con redux. Al agregar un elemento o disminuir su cantidad, envío una acción que actualiza el estado. En mi reductor, si el producto aún no se ha agregado, devuelvo una nueva copia de la matriz de estado con el nuevo objeto. Si ya está agregado, itero con el mapa (que devuelve una nueva matriz) y con una copia del producto que estoy aumentando y su propiedad qty aumentó. El problema es con esta propiedad ¿Cuál es la forma correcta de actualizar esta propiedad sin mutar el estado?

Reductora

case types.ADD_TO_CART_SUCCESS:
  const isAdded = state.cart.find(item => item.sku === action.product.sku);
  if (!isAdded) action.product.qty = 1;
  return {
    ...state,
    cart: !isAdded
      ? [action.product, ...state.cart]
      : state.cart.map(item =>
          item.sku === action.product.sku
            ? { ...item, qty: ++item.qty }
            : item
        ),
  };

Advertencia de violación

Invariant Violation: A state mutation was detected inside a dispatch, in the path: `user.cart.0.qty`. Take a look at the reducer(s) handling the action {"type":"ADD_TO_CART_SUCCESS"....}
2
Pablo Navarro 26 may. 2020 a las 23:03

3 respuestas

La mejor respuesta

Usted está mutando el elemento actual. El incremento / decremento previo / posterior mutará la referencia de objeto actual en la que opera.

case types.ADD_TO_CART_SUCCESS:
  const isAdded = state.cart.find(item => item.sku === action.product.sku);
  if (!isAdded) action.product.qty = 1;
  return {
    ...state,
    cart: !isAdded
      ? [action.product, ...state.cart]
      : state.cart.map(item =>
          item.sku === action.product.sku
            ? { ...item, qty: ++item.qty } // <-- This mutates the current item.qty
            : item
        ),
  };

Sin embargo, puede devolver un valor incrementado, actual item.qty + 1

case types.ADD_TO_CART_SUCCESS:
  const isAdded = state.cart.find(item => item.sku === action.product.sku);
  if (!isAdded) action.product.qty = 1;
  return {
    ...state,
    cart: !isAdded
      ? [action.product, ...state.cart]
      : state.cart.map(item =>
          item.sku === action.product.sku
            ? { ...item, qty: item.qty + 1 }
            : item
        ),
  };

Sospecho que su código funciona como se esperaba, ya que lo hace reemplaza correctamente toda la matriz del carrito y el artículo que muta es el artículo que desea actualizar de todos modos, pero esto es indicativo de que su reductor puede no ser una función pura , es decir, el estado anterior debería ser igual al estado anterior en todos los aspectos después de que el reductor calcula el siguiente estado.

3
Drew Reese 26 may. 2020 a las 20:14

Parece que está utilizando nuestro paquete oficial Redux Toolkit, que advertirá sobre mutaciones de estado accidentales por defecto . Eso es genial, porque claramente captó una mutación en este caso.

Pero, lo que es aún mejor es que Redux Toolkit usa Immer en el interior para permitirle escribir actualizaciones de estado "mutantes" en reductores. Eso significa que su lógica reductora se puede simplificar a esto:

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addToCartSuccess(state, action) {
      const item = state.cart.find(item => item.sku === action.payload.product.sku);
      if (item) {
        item.qty++;
      } else {
        action.product.qty = 1;
        state.cart.unshift(action.payload.product);
      }
    }
  }
})
1
markerikson 27 may. 2020 a las 14:04

Hice algo como esto un par de veces, intente este código:

const addItemToCart = (cartItems, cartItemToAdd) => {
  const existingCartItem = cartItems.find(
    (cartItem) => cartItem.id === cartItemToAdd.id
  );

  if (existingCartItem) {
    return cartItems.map((cartItem) =>
      cartItem.id === cartItemToAdd.id
        ? { ...cartItem, quantity: cartItem.quantity + 1 }
        : cartItem
    );
  }

  return [...cartItems, { ...cartItemToAdd, quantity: 1 }];
};
0
Puk 26 may. 2020 a las 23:02