0

I've been having trouble using React's useContext hook. I'm trying to update a state I got from my context, but I can't figure out how. I manage to change the object's property value I wanted to but I end up adding another object everytime I run this function. This is some of my code:

A method inside my "CartItem" component.

const addToQuantity = () => {
    cartValue.forEach((item) => {
      let boolean = Object.values(item).includes(props.name);
      console.log(boolean);
      if (boolean) {
        setCartValue((currentState) => [...currentState, item.quantity++])
      } else {
        return null;
      }
    });
  };

The "Cart Component" which renders the "CartItem"

  const { cart, catalogue } = useContext(ShoppingContext);
  const [catalogueValue] = catalogue;
  const [cartValue, setCartValue] = cart;

  const quantiFyCartItems = () => {
    let arr = catalogueValue.map((item) => item.name);
    let resultArr = [];
    arr.forEach((item) => {
      resultArr.push(
        cartValue.filter((element) => item === element.name).length
      );
    });
    return resultArr;
  };

  return (
    <div>
      {cartValue.map((item, idx) => (
        <div key={idx}>
          <CartItem
            name={item.name}
            price={item.price}
            quantity={item.quantity}
            id={item.id}
          />
          <button onClick={quantiFyCartItems}>test</button>
        </div>
      ))}
    </div>
  );
};

So how do I preserve the previous objects from my cartValue array and still modify a single property value inside an object in such an array?

edit: Here's the ShoppingContext component!

import React, { useState, createContext, useEffect } from "react";
import axios from "axios";

export const ShoppingContext = createContext();

const PRODUCTS_ENDPOINT =
  "https://shielded-wildwood-82973.herokuapp.com/products.json";

const VOUCHER_ENDPOINT =
  "https://shielded-wildwood-82973.herokuapp.com/vouchers.json";

export const ShoppingProvider = (props) => {
  const [catalogue, setCatalogue] = useState([]);
  const [cart, setCart] = useState([]);
  const [vouchers, setVouchers] = useState([]);

  useEffect(() => {
    getCatalogueFromApi();
    getVoucherFromApi();
  }, []);

  const getCatalogueFromApi = () => {
    axios
      .get(PRODUCTS_ENDPOINT)
      .then((response) => setCatalogue(response.data.products))
      .catch((error) => console.log(error));
  };

  const getVoucherFromApi = () => {
    axios
      .get(VOUCHER_ENDPOINT)
      .then((response) => setVouchers(response.data.vouchers))
      .catch((error) => console.log(error));
  };

  return (
    <ShoppingContext.Provider
      value={{
        catalogue: [catalogue, setCatalogue],
        cart: [cart, setCart],
        vouchers: [vouchers, setVouchers],
      }}
    >
      {props.children}
    </ShoppingContext.Provider>
  );
};

edit2: Thanks to Diesel's suggestion on using map, I came up with this code which is doing the trick!

   const newCartValue = cartValue.map((item) => {
     const boolean = Object.values(item).includes(props.name);
     if (boolean && item.quantity < item.available) {
       item.quantity++;
     }
     return item;
   });
   removeFromStock();
   setCartValue(() => [...newCartValue]);
 };```

1 Answers1

0

I'm assuming that you have access to both the value and the ability to set state here:

const addToQuantity = () => {
    cartValue.forEach((item) => {
      let boolean = Object.values(item).includes(props.name);
      console.log(boolean);
      if (boolean) {
        setCartValue((currentState) => [...currentState, item.quantity++])
      } else {
        return null;
      }
    });
  };

Now... if you do [...currentState, item.quantity++] you will always add a new item. You're not changing anything. You're also running setCartValue on each item, which isn't necessary. I'm not sure how many can change, but it looks like you want to change values. This is what map is great for.

const addToQuantity = () => {
    setCartValue((previousCartValue) => {
      const newCartValue = previousCartValue.map((item) => {
        const boolean = Object.values(item).includes(props.name);
        console.log(boolean);
        if (boolean) {
          return item.quantity++;
        } else {
          return null;
        }
      });
      return newCartValue;
    });
  };

You take all your values, do the modification you want, then you can set that as the new state. Plus it makes a new array, which is nice, as it doesn't mutate your data.

Also, if you know only one item will ever match your criteria, consider the .findIndex method as it short circuits when it finds something (it will stop there), then modify that index.

Diesel
  • 5,099
  • 7
  • 43
  • 81
  • Nice idea on the map!! But it seems to be taking up a lot of memory. I've tried doing mapping inside a setState before and it usually crashes my app. I've implemented this code and it seems to be working right now!! ``` const addToQuantity = () => { const newCartValue = cartValue.map((item) => { const boolean = Object.values(item).includes(props.name); console.log(boolean); if (boolean) { item.quantity++; } return item; }) setCartValue(() => [...newCartValue]); }; ``` – Matheus Ciappina Pereira Jul 10 '20 at 05:13