1

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 :(

nuser137
  • 401
  • 2
  • 8

2 Answers2

0

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.

ClusterH
  • 761
  • 3
  • 16
  • Can you please provide the updated demo so that I could understand? like I know by the code you posted but I will get to properly what is happening if there is a demo :( – nuser137 Jun 22 '23 at 17:23
  • Thank you for your answer, as I got the first answer works for me. – nuser137 Jun 22 '23 at 17:43
0

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>
    </>
  );
}
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • 1
    Thanks a lot to give awareness of hard-coding magic numbers. And it works...!! thank you so much I was so much thinking about this but don't know how to implement it. anyways, thanks! – nuser137 Jun 22 '23 at 17:41
  • `setHeartsClicked((existing) => { const copy = [...existing]; copy[index] = !copy[index]; return copy; });` can you explain what is going on with this snippet? I want to know. – nuser137 Jun 22 '23 at 17:49
  • 1
    @nuser137 Since React likes everything immutable, the first line just creates a copy of the existing `heartsClicked` array. The next line inverts the truthiness of the value at the current index in the array. The return statement provides the new state to the `heartsClicked` state. – Mr. Polywhirl Jun 22 '23 at 18:07
  • Thank you for your quick response and clarification! – nuser137 Jun 22 '23 at 18:15