skip to Main Content

In nextJs I created a provider for useState, now I am able to add items to the cart, but that’s all that I can do…

cart = [{product: "product name", count:1}]

When I want to add an item to this I can do

setCart([...cart, {product:'new one', count:1}])

But I am just not able to write code that will update the count of existing items in the cart…

I don’t know how…

3

Answers


  1. You cannot just spread the new item. You need to check the existing cart and update according.

    In the React demo below, you can click the buttons to add the items to the cart.

    const { useCallback, useEffect, useState } = React;
    
    const fetchProducts = Promise.resolve([
      { name: 'Apple' }, { name: 'Banana' }, { name: 'Pear' }
    ]);
    
    const App = () => {
      const [products, setProducts] = useState([]);
      const [cart, setCart] = useState([]);
    
      const addToCart = useCallback((e) => {
        const product = e.target.textContent;
        setCart((existingCart) => {
          const cartCopy = structuredClone(existingCart);
          let existing = cartCopy.find((item) => item.product === product);
          if (existing) {
            existing.count += 1;
          } else {
            cartCopy.push({ product, count: 1 });
          }
          return cartCopy;
        });
      }, []);
    
      useEffect(() => {
        fetchProducts.then(setProducts);
      }, []);
    
      return (
        <div>
          <h2>Products</h2>
          {products.map(({ name }) => (
            <button key={name} onClick={addToCart}>{name}</button>
          ))}
          <h2>Cart</h2>
          <ul>
            {cart.map(({ product, count }) => (
              <li key={product}>{`${product} ×${count}`}</li>
            ))}
          </ul>
        </div>
      );
    };
    
    ReactDOM
      .createRoot(document.getElementById("root"))
      .render(<App />);
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
  2. Because React uses Object.is to compare equality we need to ensure that the new object we pass to setState has a different reference.

    I’d do this with a little custom hook. It could look something like so:

    function useStateArray<T>(initialVal: T[]) {
      const [arr, setArr] = useState(initialVal);
    
      const push = React.useCallback(
        (val: T) => setArr((old) => [...old, val]),
        []
      );
      const edit = React.useCallback((val: Partial<T>, index: number) => {
        setArr((old) => {
          const newArr = [...old];
          newArr[index] = { ...newArr[index], ...val };
          // We need to return a new array to update the state!
          return newArr;
        });
      }, []);
    
      return [arr, push, edit, setArr];
    }
    

    The idea being that if you’re using arrays in your useState a lot it might make sense to have a few helper functions ready to go.

    You could then add some other utility functions (like delete, pop, etc).

    Login or Signup to reply.
  3. You’re probably better off using useReducer vs. useState, due to the complexity of your model. Try something like this:

    function cartReducer(oldCart, action) {
        switch(action.type) {
            case 'new':
                // we don't have anything unique like id, so prevent collisions on name
                if (oldCart.some(item => item.name === action name)) return oldCart;
                return [...oldCart, {name: action.name, count: 1}];
            case 'increment':
                return oldCart.map((item) => item.name === action.name ? {...item, count: item.count + 1} : item);
            case 'decrement':
                return oldCart.map((item) => item.name === action.name ? {...item, count: item.count - 1} : item);
            default:
                return oldCart
        }
    }
    
    function YourComponent(props) {
        const [cart, dispatch] = useReducer(cartReducer, []);
        function createItem(name) {
            dispatch({type: 'new', name});
        }
    
        function incrementItemCount(item) {
            dispatch({type: 'increment', name: item.name});
        }
    
        function decrementItemCount(item) {
            dispatch({type: 'decrement', name: item.name});
        }
        //render logic here
    }
    

    However, useState has a form that lets you get at the old version of the state if you want to do it that way.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search