skip to Main Content

I’m building a fast-paced game where the user collects animals through matching. The game works as follows: The user is presented with 2 pictures of 2 animals, and they must cycle through the pictures by pressing 2 buttons, one that changes the top picture and one that changes the bottom. When the top picture and bottom picture match, that animal is collected and put in the user’s bestiary.

You can try it here: https://animal-snipet.web.app/

I’m trying to filter data from animalData that corresponds with the user’s caught animals so that these animals don’t show up in the next game. Ideally, the code snippet below would push a filtered array that consists of animals the user hasn’t caught yet, and then, in the next game, animal data would be assigned to this new array. All my attempts at coding this have resulted in the top and bottom indexes rapidly switching through all the pictures and breaking my app.

Here are the relevant code snippets
//The code that executes whenever a button is clicked. There’s an identical function named voteTop that mirros what voteBottom does.

import React, { useState, useEffect } from "react";

function App() {
  const [animalData, setAnimalData] = useState(["Dog", "Cat", "Bird"]); // Sample data
  const [matchedAnimals, setMatchedAnimals] = useState([]);
  
  // Simulating vote function
  const vote = () => {
    const randomIndex = Math.floor(Math.random() * animalData.length);
    const matchedAnimal = animalData[randomIndex];
    setMatchedAnimals(prevMatchedAnimals => [...prevMatchedAnimals, matchedAnimal]);
    setAnimalData(prevAnimalData => prevAnimalData.filter((_, index) => index !== randomIndex));
  };
  
  return (
    <div>
      <h2>Animal Data</h2>
      <ul>
        {animalData.map((animal, index) => (
          <li key={index}>{animal}</li>
        ))}
      </ul>
      <button onClick={vote}>Vote</button>
      <h2>Matched Animals</h2>
      <ul>
        {matchedAnimals.map((animal, index) => (
          <li key={index}>{animal}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

  

Firebase Setup:
I’m using Firebase to store the animal data and matched animals. Here’s how the data is structured:

  • animalData array: stored in the Firebase Realtime Database under the "unclaimed" node.
  • matchedAnimals array: stored in the Firebase Realtime Database under the "matchedAnimals" node.

I’m using the Firebase SDK to read and write data to these nodes.

Expected behavior:
After a game ends, the previously matched animals should be filtered out from the animalData array, so that the next game starts with a fresh set of animals.

2

Answers


  1. I have no idea what your objects look like but, it seems like you could filter possible matched cards with ids and still keep it O(1) the 1 being the total list of animals

    Login or Signup to reply.
  2. Instead of trying to do all the work in a button’s onClick handler that is effectively duplicated across two vote button’s callbacks, I suggest adding some React state that holds the randomly generated indices and the button callbacks just update these state. Move the logic to test for matches between button/voting indices into a useEffect hook.

    Example:

    const [randIndex1, setRandIndex1] = useState(() => getRandIndex(animalData));
    const [randIndex2, setRandIndex2] = useState(() => getRandIndex(animalData));
    
    useEffect(() => {
      // Check if there are animals to match, and that the voted indices match
      if (animalData.length && randIndex1 === randIndex2) {
        const matched = animalData[randIndex1];
    
        // Remove the matched animal from the animals array
        const nextAnimals = animalData.toSpliced(randIndex1, 1);
        setAnimalData(nextAnimals);
    
        // Append matched animal to the matched animals array
        setMatchedAnimals((matchedAnimals) => matchedAnimals.concat(matched));
    
        // Recompute two new random array indices to select
        // new random animals to match for the next round
        setRandIndex1(getRandIndex(nextAnimals));
        setRandIndex2(getRandIndex(nextAnimals));
      }
    }, [animalData, randIndex1, randIndex2]);
    
    const vote1Handler = () => setRandIndex1(getRandIndex(animalData));
    const vote2Handler = () => setRandIndex2(getRandIndex(animalData));
    

    Full Demo:

    const data = ["Dog", "Cat", "Bird", "Pig", "Shark", "Worm", "Eagle", "Lion"];
    
    const getRandIndex = (arr) => Math.floor(Math.random() * arr.length);
    
    function App() {
      const [animalData, setAnimalData] = React.useState(data);
      const [matchedAnimals, setMatchedAnimals] = React.useState([]);
    
      const [randIndex1, setRandIndex1] = React.useState(() => getRandIndex(animalData));
      const [randIndex2, setRandIndex2] = React.useState(() => getRandIndex(animalData));
    
      React.useEffect(() => {
        if (animalData.length && randIndex1 === randIndex2) {
          const matched = animalData[randIndex1];
          const nextAnimals = animalData.toSpliced(randIndex1, 1);
          setAnimalData(nextAnimals);
          setMatchedAnimals((matchedAnimals) => matchedAnimals.concat(matched));
          setRandIndex1(getRandIndex(nextAnimals));
          setRandIndex2(getRandIndex(nextAnimals));
        }
      }, [animalData, randIndex1, randIndex2]);
    
      const vote1Handler = () => setRandIndex1(getRandIndex(animalData));
      const vote2Handler = () => setRandIndex2(getRandIndex(animalData));
    
      const reset = () => {
        setAnimalData(data);
        setMatchedAnimals([]);
        setRandIndex1(getRandIndex(data));
        setRandIndex2(getRandIndex(data));
      };
    
      return (
        <div>
          <h2>Animal Data</h2>
          <div>{animalData.join(", ")}</div>
    
          {animalData.length ? (
            <div className="vote-container">
              <button type="button" onClick={vote1Handler}>
                {animalData[randIndex1]}
              </button>
              <button type="button" onClick={vote2Handler}>
                {animalData[randIndex2]}
              </button>
            </div>
          ) : (
            <button type="button" onClick={reset}>
              Reset
            </button>
          )}
    
          <h2>Matched Animals</h2>
          <div>{matchedAnimals.join(", ")}</div>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    .App {
      font-family: sans-serif;
      text-align: center;
    }
    
    .vote-container {
      display: flex;
      flex-direction: row;
      gap: 1rem;
      padding: 1rem;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <div id="root" />
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search