Search code examples
reactjsonclicktarget

How to change state on individual parts of a mapped arrray in React


I've got a mapped array thats set up to look like playing cards. When I click on them I need them to 'flip'. Right now my onclick 'works' and the state/property goes from 'faceup' to 'facedown' but it does it for all of the divs rather than the individual one ive clicked.

I think maybe them all having the same usestate could be the issue or my conditional statement needs to be more specific. Im very new to react so any help is appreciated.

function DrawCards() {
  const [flip, setFlip] = React.useState("faceup");
  let values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
  let heart = (
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="red" className="bi bi-heart-fill" viewBox="0 0 16 16">
      <path fillRule="evenodd" d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314" />
    </svg>
  );
  let spade = (
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="black" className="bi bi-suit-spade-fill" viewBox="0 0 16 16">
      <path d="M7.184 11.246A3.5 3.5 0 0 1 1 9c0-1.602 1.14-2.633 2.66-4.008C4.986 3.792 6.602 2.33 8 0c1.398 2.33 3.014 3.792 4.34 4.992C13.86 6.367 15 7.398 15 9a3.5 3.5 0 0 1-6.184 2.246 20 20 0 0 0 1.582 2.907c.231.35-.02.847-.438.847H6.04c-.419 0-.67-.497-.438-.847a20 20 0 0 0 1.582-2.907" />
    </svg>
  );
  let club = (
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="black" className="bi bi-suit-club-fill" viewBox="0 0 16 16">
      <path d="M11.5 12.5a3.5 3.5 0 0 1-2.684-1.254 20 20 0 0 0 1.582 2.907c.231.35-.02.847-.438.847H6.04c-.419 0-.67-.497-.438-.847a20 20 0 0 0 1.582-2.907 3.5 3.5 0 1 1-2.538-5.743 3.5 3.5 0 1 1 6.708 0A3.5 3.5 0 1 1 11.5 12.5" />
    </svg>
  );
  let diamond = (
    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="red" className="bi bi-suit-diamond-fill" viewBox="0 0 16 16">
      <path d="M2.45 7.4 7.2 1.067a1 1 0 0 1 1.6 0L13.55 7.4a1 1 0 0 1 0 1.2L8.8 14.933a1 1 0 0 1-1.6 0L2.45 8.6a1 1 0 0 1 0-1.2" />
    </svg>
  );
  const flipCard = () => {
    setFlip("facedown");
    {
      flip === "facedown" && setFlip("faceup");
    }
  };

  const hearts = values.map((hcard, index) => (
    <div onClick={flipCard} key={index} face={flip} className="card">
      {hcard}
      {heart}
    </div>
  ));
  const spades = values.map((scard, index) => (
    <div key={index} className="card">
      {scard}
      {spade}
    </div>
  ));
  const clubs = values.map((ccard, index) => (
    <div key={index} className="card">
      {ccard}
      {club}
    </div>
  ));
  const diamonds = values.map((dcard, index) => (
    <div key={index} className="card">
      {dcard}
      {diamond}
    </div>
  ));
  console.log(Hearts);
  return (
    <div className="table">
      <div className="row">{hearts}</div>
    </div>
  );
}

Solution

  • I think maybe them all having the same useState could be the issue

    Yes, it is.

    Each card of each suit should have an independent state. However, it doesn't require an independent function for each suit, you can have a shared function renderCards and pass the suit type and the icon needed to that function.

    Create a shared array with the values initialFlipState filled with 13 "faceup" strings, then you can create an object state containing all suits with each suit having an array of initialFlipState.

    Now on any card click you will check if the card has faceup then change it to facedown and vice-versa.

    I have added CSS classes for demonstration.

    import React, { useState } from "react";
    
    function DrawCards() {
      const initialFlipState = Array(13).fill("faceup");
      const [flipStates, setFlipStates] = useState({
        hearts: initialFlipState,
        spades: initialFlipState,
        clubs: initialFlipState,
        diamonds: initialFlipState,
      });
    
      let values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
    
      let heart = (
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="red" className="bi bi-heart-fill" viewBox="0 0 16 16">
          <path fillRule="evenodd" d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314" />
        </svg>
      );
      let spade = (
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="black" className="bi bi-suit-spade-fill" viewBox="0 0 16 16">
          <path d="M7.184 11.246A3.5 3.5 0 0 1 1 9c0-1.602 1.14-2.633 2.66-4.008C4.986 3.792 6.602 2.33 8 0c1.398 2.33 3.014 3.792 4.34 4.992C13.86 6.367 15 7.398 15 9a3.5 3.5 0 0 1-6.184 2.246 20 20 0 0 0 1.582 2.907c.231.35-.02.847-.438.847H6.04c-.419 0-.67-.497-.438-.847a20 20 0 0 0 1.582-2.907" />
        </svg>
      );
      let club = (
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="black" className="bi bi-suit-club-fill" viewBox="0 0 16 16">
          <path d="M11.5 12.5a3.5 3.5 0 0 1-2.684-1.254 20 20 0 0 0 1.582 2.907c.231.35-.02.847-.438.847H6.04c-.419 0-.67-.497-.438-.847a20 20 0 0 0 1.582-2.907 3.5 3.5 0 1 1-2.538-5.743 3.5 3.5 0 1 1 6.708 0A3.5 3.5 0 1 1 11.5 12.5" />
        </svg>
      );
      let diamond = (
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="red" className="bi bi-suit-diamond-fill" viewBox="0 0 16 16">
          <path d="M2.45 7.4 7.2 1.067a1 1 0 0 1 1.6 0L13.55 7.4a1 1 0 0 1 0 1.2L8.8 14.933a1 1 0 0 1-1.6 0L2.45 8.6a1 1 0 0 1 0-1.2" />
        </svg>
      );
    
      const flipCard = (suit, index) => {
        setFlipStates((prevState) => ({
          ...prevState,
          [suit]: prevState[suit].map((state, i) => (i === index ? (state === "faceup" ? "facedown" : "faceup") : state)),
        }));
      };
    
      const renderCards = (suit, icon) =>
        values.map((card, index) => (
          <div onClick={() => flipCard(suit, index)} key={index} face={flipStates[suit][index]} className={`card ${flipStates[suit][index]}`}>
            {card} {icon}
          </div>
        ));
    
      return (
        <div className="table">
          <div className="row">{renderCards("hearts", heart)}</div>
          <div className="row">{renderCards("spades", spade)}</div>
          <div className="row">{renderCards("clubs", club)}</div>
          <div className="row">{renderCards("diamonds", diamond)}</div>
        </div>
      );
    }
    
    export default DrawCards;
    

    Here is a working example on StackBlitz