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.