skip to Main Content

I have a Carousel Component and want to have a "like" button on each card. How could I do this in the function component? When I use "useState" on the "like" button and the reaction appears on each child (each card). How I could handle this?

I would like when I click on the "like" button the new counting of likes to be increased separately for each one, and only the reaction to be shown to the particular child (card).
<> <Card name="A" likes={0} /> <Card name="B" likes={1} clicked /> </>

// FlashDeals.jsx
import React, { useState } from "react";
import Slider from "react-slick";

export const FlashCard = ({ productItems }) => {
  const [count, setCount] = useState(0);
  const increment = (prev) => {
    setCount((prev) => (prev += 1));
  };

  const settings = {
    dots: false,
    infinite: true,
    speed: 500,
    slidesToShow: 4,
    slidesToScroll: 1,
  };

  return (
    <>
      <Slider {...settings}>
        {productItems.map((item) => {
          return (
            <div className="box" key={item.id}>
              <div className="product">
                <div className="img">
                  <span className="discount">{item.discount}% Off</span>
                  <img src={item.cover} alt="" />
                  <div className="product-like">
                    <label>{count}</label>
                    <br />
                    <i className="fa-regular fa-heart" onClick={increment}></i>
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </Slider>
    </>
  );
};

2

Answers


  1. count is a single integer. Which card should that value apply to? You’re essentially trying to use one number to count many things.

    Which also means you’ve mis-named your component. It’s called a "flashcard" but holds many "cards", it contains a value for one "count" but is expected to have many "counts". Overall you’re basically confusing "single" with "plural".

    Separate the concepts. Have a parent component which holds the cards, and have each card maintain its state. For example, consider this card component:

    export const Card = ({ item }) => {
      const [count, setCount] = useState(0);
      const increment = () => {
        setCount((prev) => (prev += 1));
      };
    
      return (
        <div className="box">
          <div className="product">
            <div className="img">
              <span className="discount">{item.discount}% Off</span>
              <img src={item.cover} alt="" />
              <div className="product-like">
                <label>{count}</label>
                <br />
                <i className="fa-regular fa-heart" onClick={increment}></i>
              </div>
            </div>
          </div>
        </div>
      );
    };
    

    This Card component displays on "card" based on the item passed to it, and internally tracks the count of how many times it has been clicked.

    Then the parent component can just display the cards:

    import React, { useState } from "react";
    import Slider from "react-slick";
    
    export const FlashCards = ({ productItems }) => {
      const settings = {
        dots: false,
        infinite: true,
        speed: 500,
        slidesToShow: 4,
        slidesToScroll: 1,
      };
    
      return (
        <>
          <Slider {...settings}>
            {productItems.map((item) => {
              return <Card item={item} key={item.id} />;
            })}
          </Slider>
        </>
      );
    };
    
    Login or Signup to reply.
  2. I like David’s answer, it keeps your code clean and easy to work with. However, in some cases you might need the Card count on the parent component, say, for instance, to filter cards based on their count.

    In these cases, you can use an array of counts in the parent component, and you’d increment the count of each card based on their original index, and pass this count to each card as prop. Like so:

    import React, { useState } from "react";
    
    export const FlashCards = ({ productItems }) => {
      const settings = {
        dots: false,
        infinite: true,
        speed: 500,
        slidesToShow: 4,
        slidesToScroll: 1,
      };
      
      // all cards starting with count 0
      const [cardCounts, setCardCounts] = useState([...new Array(productItems.length)].map((_elem) => 0)
    
      const onIncrement = (cardIndex) => {
        // increment just the one card in our array and keep everything else the same
        setCardCounts((prev) => [
           ...prev.slice(0, cardIndex),
           prev[cardIndex] + 1,
           ...prev.slice(cardIndex + 1)
        ])
      }
      
      return (
        <>
          <Slider {...settings}>
            {productItems.map((item, cardIndex) => {
              return <Card item={item} key={item.id} onClickIncrement={() => onIncrement(cardIndex)} count={cardCounts[cardIndex]} />;
            })}
          </Slider>
        </>
      );
    };
    

    And then just call the function onClickIncrement() inside the Card component when you desire, and display the count as you wish, getting both from its props:

    const Card = ({ card, onClickIncrement, count }) => {
       // ...
    }
    

    The code becomes a bit more cumbersome, but I think this logic comes across handy in a lot of use cases.

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