skip to Main Content

I want to select multiple hearts as per user will going to select. In other words, When a user clicks on multiple hearts then multiple hearts should be selected or the background color should be green. However, it is selecting one at a time.

live demo

I know that if multiple hearts will select then needs to store them in an array and map through it, then the nesting map will increase more hearts as the user will click.

Is there any way to solve this issue? I am stuck for a lot of hours 🙁

2

Answers


  1. Simply you can have one state which is storing selected hearts index.
    For example:

    const [selectedHeartIndex, setSelectedHeartIndex] = useState([])
    

    And whenever click every heart, check those index included in above index Array or not. If included, it means current heart is already selected, and you are trying to click it again to be non-selected, so you need to remove that index from the selectedHeartIndex array, and if not included, it means, it’s not selected before, so you need to add those index to that array to select that heart. Here is the code.

    const handleClick = (index) => {
       const isSelected = selectedHeartIndex.filter((inx) => inx === index).length > 0
       if (isSelected) setSelectedHeartIndex(selectedHeartIndex.filter((inx) => inx !== index))
       else setSelectedHeartIndex((prev) => [...prev, index])
    }
    

    And finally, pass heartIndexClicked props to your child component(EachHeart) with this boolean value selectedHeartIndex.filter((inx) => inx === index).length > 0

    You need not have isLike and heartIndexClicked state in your code.

    Login or Signup to reply.
  2. You should only have to care about the index of the item when it comes to checking the state.

    <EachHeart
      checked={heartsClicked[index]}
      onClick={handleClick}
      index={index}
    />
    
    const handleClick = (index) => {
      setHeartsClicked((existing) => {
        const copy = [...existing];
        copy[index] = !copy[index];
        return copy;
      });
    };
    

    Also, these states are pointless:

    const [isLike, setIsLike] = useState(false);
    const [heartIndexClicked, setHeartIndexClicked] = useState();
    

    Working example

    Try the following:

    Note: I changed first10 to visibleData. It’s a better name that I already suggested yesterday. I also changed the size from 10 to 15. You should use a variable instead of hard-coding magic numbers.

    import { useCallback, useMemo, useState } from "react";
    import InfiniteScroll from "react-infinite-scroll-component";
    import { TbHeart } from "react-icons/tb";
    import { arr } from "./utils";
    
    export function EachHeart({ checked, index, onClick }) {
      return (
        <>
          <TbHeart
            key={index}
            onClick={() => onClick(index)}
            className={` mt-9 ${
              checked &&
              "fill-green-500 transition-colors ease-in delay-150 hover:scale-[1.20]"
            } stroke-green-500 ml-5 w-5 h-5 cursor-pointer`}
          />
        </>
      );
    }
    
    const pageSize = 15;
    
    export default function App() {
      const [visibleData, setVisibleData] = useState(arr.slice(0, pageSize));
      const [page, setPage] = useState(1);
      const [heartsClicked, setHeartsClicked] = useState([]);
    
      const fetchMoreData = useCallback(() => {
        setTimeout(() => {
          setVisibleData((prev) => [
            ...prev,
            ...arr.slice(page * pageSize, page * pageSize + pageSize),
          ]);
          setPage((prev) => (prev += 1));
        }, 2000);
      }, []);
    
      const handleClick = useCallback((index) => {
        setHeartsClicked((existing) => {
          const copy = [...existing];
          copy[index] = !copy[index]; // Invert true/false
          return copy;
        });
      }, []);
    
      const isDone = useMemo(() => visibleData.length < arr.length, [visibleData]);
    
      return (
        <>
          <div className="mt-24"></div>
          <InfiniteScroll
            dataLength={visibleData.length}
            next={fetchMoreData}
            hasMore={isDone}
            loader={<h3 className="font-bold text-center text-xl">Loading...</h3>}
            endMessage={
              <p className="text-base my-4 font-medium text-center">
                <b>Yay! You have seen it all</b>
              </p>
            }
          >
            {visibleData.map((t, index) => {
              return (
                <>
                  <div className="flex ">
                    <li key={index} className="mx-4 mt-8">
                      {t.name.concat(` ${t.id}`)}
                    </li>
                    <EachHeart
                      checked={heartsClicked[index]}
                      onClick={handleClick}
                      index={index}
                    />
                  </div>
                </>
              );
            })}
          </InfiniteScroll>
        </>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search