Home > Enterprise >  React - change styling of component onClick and reset
React - change styling of component onClick and reset

Time:10-01

So i am trying to implement a bingo game as a small starter react project. If a user gets a word then i want them to be able to click the box and the styling changes so that the box is highlighted green instead for example.

My current implementation of this sort of works and it changes the colour of the boxes but when i try to reset and press 'new game' some of the boxes remain highlighted and aren't reset.

I've tried passing in a reset prop to the component to reset the state but this hasnt worked so im left quite confused...

Any ideas on what i could do instead?

Here is my app.js

import { useState, useEffect } from 'react'
import './App.css';
import Cell from './components/Cell';

function App() {
  const [words, setWords] = useState([])
  const [reset, setReset] = useState(false)

  const groupOfWords = [
    { "word": "hello", id: 1},
    { "word": "react", id: 2},
    { "word": "gaming", id: 3},
    { "word": "university", id: 4},
    { "word": "yoooo", id: 5},
    { "word": "hockey", id: 6},
    { "word": "programming", id: 7},
    { "word": "xbox", id: 8},
    { "word": "digging", id: 9},
    { "word": "car", id: 10}
  ]

  const pickRandomWords = () => {
    setReset(true)

    // Shuffle array
    const shuffled = groupOfWords.sort(() => 0.5 - Math.random())

    // Get sub-array of first n elements after shuffled
    setWords(shuffled.slice(0, 8))
  }

  return (
    <div className="App">
      <h1>Bingo</h1>
      <button onClick={pickRandomWords}>New Game</button>
      <div  className='grid'>
        {words.map(w => (
            <Cell 
              key={w.id}
              word={w.word}
              reset={reset}/>
        ))}
      </div>
    </div>
  );
}

export default App;

this is my cell component

import './Cell.css'
import { useState } from 'react'

export default function Cell({ word, reset }) {
    const [matched, setMatched] = useState(reset)

    const highlightCell = () => {
        setMatched(true)
    }

    return (
        <div className={matched ? 'cell' : 'cellMatched'} onClick={highlightCell}>
            <p>{word}</p>
        </div>
    )
}

CodePudding user response:

The problem is that the same value for reset is being passed to the element. You can pass new values each time you reset, like the current time, and use useEffect to catch that change.

import React from 'react';
import { useState, useEffect } from 'react'
import './style.css';

export function App(props) {
  const [words, setWords] = useState([])
  const [reset, setReset] = useState(false)

  const groupOfWords = [
    { "word": "hello", id: 1},
    { "word": "react", id: 2},
    { "word": "leidos", id: 3},
    { "word": "university", id: 4},
    { "word": "strathclyde", id: 5},
    { "word": "hockey", id: 6},
    { "word": "programming", id: 7},
    { "word": "xbox", id: 8},
    { "word": "hydrocarbon", id: 9},
    { "word": "car", id: 10}
  ]

  const pickRandomWords = () => {
    setReset(new Date())

    // Shuffle array
    const shuffled = groupOfWords.sort(() => 0.5 - Math.random())

    // Get sub-array of first n elements after shuffled
    setWords(shuffled.slice(0, 8))
  }

  return (
    <div className="App">
      <h1>Bingo</h1>
      <button onClick={pickRandomWords}>New Game</button>
      <div  className='grid'>
        {words.map(w => (
            <Cell 
              key={w.id}
              word={w.word}
              reset={reset}/>
        ))}
      </div>
    </div>
  );
}

function Cell({ word, reset }) {
    const [matched, setMatched] = useState(false)

    const highlightCell = () => {
        setMatched(true)
    }

    useEffect(()=>{

      console.log('reset on', reset)
      setMatched(false)

    },[reset])

    return (
        <div className={matched ? 'cellMatched' : 'cell'} onClick={highlightCell}>
            <p>{word}</p>
        </div>
    )
}

CodePudding user response:

Your current problem is that reset is used as the initial matched value:

const [matched, setMatched] = useState(reset)

Changing reset after the first render of Cell won't effect anything, since the initial value is no longer used.


You're passing a reset property to Cell with the intention of clearing the cell out. Assuming you get this working, your solution will follow the following concept:

Whenever we finished our bingo card we take an eraser and erase the contents of all cells. So essentially you are re-using the same bingo card again and again. Each time erasing the card when you've completed a game.


Instead of clearing the old bingo card, you could just throw it away and take a new bingo card.

In React you can force components to be created from scratch by changing their key. This concept change would also mean that Cell does not need a reset property, since a cell is thrown away when you reset the game.

Meaning that the Cell definition could look like this:

import './Cell.css';
import { useState } from 'react';

export default function Cell({ word }) {
  const [matched, setMatched] = useState(false);

  const highlightCell = () => { setMatched(true) };

  return (
    <div className={matched ? 'cell' : 'cellMatched'} onClick={highlightCell}>
      <p>{word}</p>
    </div>
  );
};

Now that we can no longer reset a cell the "game" becomes responsible for throwing away old Cell instances whenever the game is reset.

import { useState, useEffect, useCallback } from 'react';
import './App.css';
import Cell from './components/Cell';

const groupOfWords = [
  { id:  1, word: "hello"       },
  { id:  2, word: "react"       },
  { id:  3, word: "gaming"      },
  { id:  4, word: "university"  },
  { id:  5, word: "yoooo"       },
  { id:  6, word: "hockey"      },
  { id:  7, word: "programming" },
  { id:  8, word: "xbox"        },
  { id:  9, word: "digging"     },
  { id: 10, word: "car"         },
];

function App() {
  const [words,  setWords ] = useState([]);
  const [gameNr, setGameNr] = useState(0);

  const newGame = () => {
    setGameNr(gameNr => gameNr   1);

    // Shuffle array
    const shuffled = groupOfWords.sort(() => 0.5 - Math.random());

    // Get sub-array of first 8 elements
    setWords(shuffled.slice(0, 8));
  };

  return (
    <div className="App">
      <h1>Bingo</h1>
      <button onClick={newGame}>New Game</button>
      <div className='grid'>
        {words.map(({id, word}) => (
          <Cell 
            key={JSON.stringify([gameNr, id])}
            word={word}
          />
        ))}
      </div>
    </div>
  );
}

export default App;

In the above code I've replaced reset with gameNr. This number is responsible for throwing away old cells. The Cell key is now a combination of gameNr and id. So if we increment gameNr all the cells will be given a new key value. Which in turn will dump all the existing cells and initialize new Cell components.

I'm using JSON.stringify([gameNr, id]) to get a unique combination of the gameNr and id, but you can use whatever you prefer. `${gameNr}-${id}` would also do perfectly fine.

  • Related