0

I've been stuck with this idea of setting the array to an empty one after the function fires, but no matter what I did the results were the same. I want to empty an array if it contains the element that was pressed, but It keeps logging the old array, making it impossible to check if array already contains it in the first place. I'm new to React and I've already tried using useEffect() but I don't understand it that much even after studying and trying to use it several times.

import React, { useState, useEffect } from "react";
import { Card } from "./Card";
import "../styles/Playboard.css";
import { Scoreboard } from "./Scoreboard";

export function Playboard(props) {
  const [score, setScore] = useState(0);
  const [bestScore, setBestScore] = useState(0);

  const [clicked, setClicked] = useState([]);
  const [cards, setCards] = useState([
    <Card name="A" key={1} handleCardClick={handleCardClick}></Card>,
    <Card name="B" key={2} handleCardClick={handleCardClick}></Card>,
    <Card name="C" key={3} handleCardClick={handleCardClick}></Card>,
    <Card name="D" key={4} handleCardClick={handleCardClick}></Card>,
    <Card name="E" key={5} handleCardClick={handleCardClick}></Card>,
    <Card name="F" key={6} handleCardClick={handleCardClick}></Card>,
  ]);

  const increment = () => {
    setScore((c) => c + 1);
  };

  function checkScore() {
    if (score > bestScore) {
      setBestScore(score);
    }
  }

  function handleStateChange(state) {
    setClicked(state);
  }

  useEffect(() => {
    setCards(shuffleArray(cards));
    handleStateChange();
  }, []);

  useEffect(() => {
    setCards(shuffleArray(cards));
    checkScore();
  }, [score]);

  function handleCardClick(e) {
    console.log(clicked);
    if (clicked.includes(e.target.querySelector("h1").textContent)) {
      console.log(clicked);
      resetGame();
    } else {
      increment();
      clicked.push(e.target.querySelector("h1").textContent);
      console.log(clicked);
    }
  }

  function resetGame() {
    setScore(0);
    setClicked([]);
    console.log(clicked);
  }

  const shuffleArray = (array) => {
    return [...array].sort(() => Math.random() - 0.5);
  };

  return (
    <div>
      <div className="container">{cards}</div>
      <Scoreboard score={score} bestScore={bestScore}></Scoreboard>
    </div>
  );
}
2
  • Never put JSX elements in state! Just put data there and render your elements based on it. Commented Aug 24, 2022 at 14:21
  • Also, you really shouldn't (need to) do e.target.querySelector() with React. Commented Aug 24, 2022 at 14:21

1 Answer 1

1

Looks like this is a memory game of sorts, where you have to remember which cards you've clicked and they shuffle after every click? Okay!

As my comment says, you don't want to put JSX elements in state; state should be just data, and you render your UI based on that.

Here's a cleaned-up version of your game (here on CodeSandbox) –

  • Card is a simple component that renders the name of the card, and makes sure clicking the button calls handleCardClick with the name, so you need not read it from the DOM.
  • shuffleArray doesn't need to be in the component, so it's... not.
  • initializeCards returns a shuffled copy of the A-F array.
  • useState(initializeCards) takes advantage of the fact that useState allows a function initializer; i.e. initializeCards will be called once by React when Playboard mounts.
  • useEffect is used to (possibly) update bestScore when score updates. You don't really need it for anything else in this.
  • cards.map(... => <Card...>) is the usual React idiom to take data and turn it into elements.
  • I'm using JSON.stringify() here as a poor man's scoreboard plus debugging tool (for clicked).
  • I'm making liberal use of the functional update form of setState for efficiency (not that it matters a lot in this).
import React, { useState, useEffect } from "react";

function Card({ name, handleCardClick }) {
  return <button onClick={() => handleCardClick(name)}>{name}</button>;
}

function shuffleArray(array) {
  return [...array].sort(() => Math.random() - 0.5);
}

function initializeCards() {
  return shuffleArray(["A", "B", "C", "D", "E", "F"]);
}

function Playboard() {
  const [score, setScore] = useState(0);
  const [bestScore, setBestScore] = useState(0);
  const [clicked, setClicked] = useState([]);
  const [cards, setCards] = useState(initializeCards);

  useEffect(() => {
    setBestScore((bestScore) => Math.max(bestScore, score));
  }, [score]);

  function handleCardClick(name) {
    if (clicked.includes(name)) {
      resetGame();
    } else {
      setScore((c) => c + 1);
      setClicked((c) => [...c, name]);
      setCards((cards) => shuffleArray(cards));
    }
  }

  function resetGame() {
    setScore(0);
    setClicked([]);
    setCards(initializeCards());
  }

  return (
    <div>
      <div className="container">
        {cards.map((name) => (
          <Card name={name} key={name} handleCardClick={handleCardClick} />
        ))}
      </div>
      {JSON.stringify({ score, bestScore, clicked })}
    </div>
  );
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for you quality of code advices and how to use things I've been using more efficiently! I can see now why my previous version didn't work as intended, and I will surely stick to your advices for my future projects.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.