Search code examples
reactjsreact-hookscomponentsreact-context

Cannot remove from my shopping cart (Cannot update a component while rendering a different component)


I was making a shopping cart but I ran into an error, the message exactly says: Warning: Cannot update a component (StateProvider) while rendering a different component (CartDrinks). To locate the bad setState() call inside CartDrinks. I am using React Context to do it, but I can find a solution for that.

This is my Context:

import { createContext, useContext, useReducer } from "react";

export const StateContext = createContext();

export const StateProvider = ({ reducer, initialState, children }) => (
    <StateContext.Provider value={useReducer(reducer, initialState)}>
        {children}
    </StateContext.Provider>
)

export const useStateValue = () => useContext(StateContext);

and this is my Reducer:

export const initialState = {
   cart: [],
}

const reducer = (state, action) => {
   console.log(action)
   switch (action.type) {
       case "ADD_TO_CART":
           return {
               ...state,
               cart: [...state.cart, action.payload]
           };
       case "REMOVE_TO_CART":
           let newCart = [...state.cart];

           const index = state.cart.findIndex((cartItems) => cartItems.id === action.payload.id);
           newCart.splice(index, 1);
           if (index >= 0) {
               newCart.splice(index, 1);
           } else {
               alert('Cant remove drink');
           }
           return { ...state, cart: newCart };
       default:
           return state;
   }
}

export default reducer;

this is my component where i add product to cart:

import { useStateValue } from '../../context/Context';
import './card.css'

const Card = ({ id, name, image, price }) => {

    const [{ }, dispatch] = useStateValue();

    const addToCard = () => {
        dispatch({
            type: 'ADD_TO_CART',
            item: {
                id: id,
                name: name,
                image: image,
                price: price,
            }
        })
    }

    return (
        <div className="card">
            <img src={image} alt={image} />
            <div className="card__body">
                <h3>{name}</h3>
                <p>Price: $/.{price}</p>
            </div>
            <button onClick={addToCard}>Add to Cart</button>
        </div>
    )
}
export default Card

and this is my component where i remove the product from the cart

import { useStateValue } from "../../context/Context"
import './cart.css'

const CartDrinks = () => {

    const [{ cart }, dispatch] = useStateValue();

    const removeDrink = (id) => {
        dispatch({
            type: "REMOVE_TO_CART",
            id: id
        })
    }

    return (
        <div className="cart__container">
            {cart?.length === 0
                ?
                <p>You don't have nothing</p>
                :
                cart.map((i) => (
                    <div className="cart" key={i.id}>
                        <img src={i.image} alt="" />
                        <div>
                            <h4>{i.name}</h4>
                            <p>$/.{i.price}</p>*
                            <button onClick={removeDrink(i.id)}>remove</button>
                        </div>

                    </div>
                ))
            }

        </div>
    )
}
export default CartDrinks

Everything works fine until I try to enter the component where I delete the product, when entering that component I get the error.


Solution

  • The removeDrink function is being immediately invoked when the CartDrinks component mounts.

    const removeDrink = (id) => {
      dispatch({
        type: "REMOVE_TO_CART",
        id: id
      })
    }
    

    ...

    <button onClick={removeDrink(i.id)}>remove</button> // <-- immediate call
    

    Use an anonymous callback to call removeDrink when clicked.

    <button onClick={() => removeDrink(i.id)}>remove</button>
    

    Or convert removeDrink to a curried function to close over the id and return the callback.

    const removeDrink = (id) => () => {
      dispatch({
        type: "REMOVE_TO_CART",
        id: id
      })
    }
    

    ...

    <button onClick={removeDrink(i.id)}>remove</button>