skip to Main Content

I am creating an e-commerce website website and I need to create a button where the user can click and the product is added to the cart.

But before adding the product into the cart I need to show the loader inside the button so the user can wait for their product to be added to the cart.

The problem is that all the buttons are going to the loading state, instead of only the clicked one:

enter image description here

As you can see, in the above image all the loaders are shown which is not my approach.

My Code:

"use client";

const Products = () => {
  const oval = (
    // This is loader...
  );
  const dispatch = useDispatch();
  const [products, setProducts] = useState<IProducts>();
  const [loader, setLoader] = useState<JSX.Element | string>("ADD TO CART");

  useLayoutEffect(() => {
  // return products...
  }, []);

  const addToDatabase = async (product: IProducts, productId: string) => {
    setLoader(oval);
    try {
      const res = await axios.post("/api/cart", { product });
      if (res.status === 200) {
        dispatch(addProduct(product));
      } else {
        alert("Something went wrong");
      }
    } catch (error) {
      alert("INTERNAL SERVER ERROR");
      console.log(error);
    }
  };
  return (
    <>
      <div className="container">
        <div className="">
          {products &&
            products.map((product: IProducts) => (
              <div
                key={product.id}
                className=""
              >
                <Image
                  src={product.image}
                  width={100}
                  height={100}
                  alt={product.title}
                />
                <h1 className="">{product.title}</h1>
                <p className="">${product.price}</p>
                <button
                  id={`product-${product.id}`}
                  className=""
                  onClick={() =>
                    addToDatabase(product, `product-${product.id}`)
                  }
                >
                  <span>{loader}</span>
                </button>
              </div>
            ))}
        </div>
      </div>
    </>
  );
};

export default Products;

I just need to show the loader on the particular button not all the buttons.

2

Answers


  1. There are two ways I imagine you can handle this, one is to create the cards a separate component that is mapped, this way each card would have its own state of the loader

    const ParentComponent = () => {
    const list = ['list of products goes here'];
    
    return <div>
    /// other content
    list.map(child => <ChildComponent data={child} />)
    
    </div>
    }
    
    const ChildComponent = ({data}) => {
    
    const [isLoading, setIsLoading] = useState(false);
    async function addItemToCart() {
    setIsLoading(true);
    try{
    // await logic here
    }
    catch(e) {
    // logic here
    }
    setIsLoading(false)
    }
    return <div>
    // content here
    <button loading={isLoading} onClick={addItemToCard} />
    </div>
    }
    

    Other way is to create a list of items that are being currently added, in this way your component instead of having a loader true or false state, will have an array state as

    const Component = () => {
    // other stuff
    const [loadingList, setLoadingList] = useState([]);
    
    
    function addItemToCart(item) {
    if(loadingList.includes(item.id)) return;
    setLoadingList([...loadingList, item.id])
    // async logic here
    setLoadingList(oldState => {
    const newState = [...oldState];
    const index = newState.indexOf(item.id);
    if(index > -1) {
    newState.splice(index, 1);
    }
    return newState
    })
    }
    return <div>
    
    // where button is
    <button onClick={() => addItemToCart(item)} 
    loading={loadingList.includes(item.id)}
    />
    
    </div>
    }
    
    Login or Signup to reply.
  2. All the buttons are switching to the loading state because they are all referring to the same state. You could change slightly your logic, by having a loadingProductId state, like so:

    const [loadingProductId, setLoadingProductId] = useState<string>("");
    const addToDatabase = async (product: IProducts) => {
      setLoadingProductId(product.id);
      try {
        const res = await axios.post("/api/cart", { product });
        if (res.status === 200) {
          dispatch(addProduct(product));
        } else {
          alert("Something went wrong");
        }
      } catch (error) {
        alert("INTERNAL SERVER ERROR");
        console.log(error);
      } finally {
        setLoadingProductId("");
      }
    };
    
    <button id={`product-${product.id}`} onClick={() => addToDatabase(product)}>
      <span>
        {loadingProductId == product.id ? "Your loader here..." : "Add to cart"}
      </span>
    </button>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search