I’ve built a simple stopwatch with start and stop buttons. I’ve also built a really barebones matching game where a user clicks on 5 different squares that start out as random colors (possibility of being 7 predefined colors). Every time the user clicks on a square, it changes colors, and when all 5 match, it renders a message on the screen and disables any clicking changes to the squares until you press a restart button (in which it gives you a new set of random colors). It also counts the number of times you’ve clicked.
I’m wanting to add in the timing element to the matching game. For now, what I’m looking to do is have a start button that starts the timer, but have the timer automatically stop once the condition of all 5 squares matching is met (so no stop button).
Where I’m struggling is how, and where, to move the logic to stop the timer from the stop button that I currently have, to where it should be for what I’m wanting to do. My best attempt so far was getting the entire timer to disappear when all 5 squares were matching, which is definitely not what I want. Or you know, I’ve just crashed the whole thing with too many re-renders.
Can anyone point me in the right direction of how I should be looking at this?
Some of the code below:
("GroupOfSquares" component where I have the majority of the logic for this game–receives "colorsArray" from app.js)
import Stopwatch from "./Stopwatch";
import Square from "./Square";
import { useState, useEffect } from "react";
function randomPick(array) {
const index = Math.floor(Math.random() * array.length);
return array[index];
}
export default function GroupOfSquares({ colorsArray }) {
const genRandomColors = () => {
let colorArrayCopy = [...colorsArray];
let newArray = [];
for (let i = 0; i < 5; i++) {
let randNum = Math.floor(Math.random() * colorArrayCopy.length);
let splicedItem = colorArrayCopy.splice(randNum, 1)[0]
newArray.push(splicedItem);
}
return newArray;
}
genRandomColors(colorsArray);
const [count, setCount] = useState(0);
const [initialColors] = useState(genRandomColors);
const [colors, setColors] = useState(initialColors);
const [timerOn, setTimerOn] = useState(false);
const [time, setTime] = useState(0);
useEffect(() => {
let interval = null;
if (timerOn) {
interval = setInterval(() => {
setTime(prevTime => prevTime + 10)
}, 10)
} else {
clearInterval(interval)
}
return () => clearInterval(interval);
}, [timerOn])
const squares = colors.map((color, i) => (
<Square
key={i}
color={color}
onClick={() => {
setColors((prev) => {
const next = [...prev];
next[i] = randomPick(colorsArray);
return next;
});
setCount((prev) => prev + 1);
}}
/>
));
const newSquares = squares.map((square) => {
return square.props.color;
});
const allColorsEqual = function (newSquares) {
return newSquares.every(val => val === newSquares[0]);
}
const isCompleted = allColorsEqual(newSquares);
const reset = () => {
setCount(0);
setColors(genRandomColors);
setTime(0);
};
return (
<div className="Container">
<Stopwatch
Start={() => setTimerOn(true)}
Stop={() => setTimerOn(false)}
Time={time}
/>
<div
className="RowOfSquares"
style={{ pointerEvents: isCompleted ? 'none' : 'auto' }}
>
{squares}
</div>
{isCompleted && <h3>Matched!</h3>}
<div className="clicks">
<div className="numClicks">
<span>{count} Clicks</span>
</div>
<button onClick={reset}>reset</button>
</div>
</div>
);
}
Stopwatch component:
export default function Stopwatch({ Start, Time, Stop }) {
return (
<div>
<div>
<span>{("0" + Math.floor((Time / 60000) % 60)).slice(-2)}:</span>
<span>{("0" + Math.floor((Time / 1000) % 60)).slice(-2)}:</span>
<span>{("0" + ((Time / 10) % 100)).slice(-2)}</span>
</div>
<div className="buttons">
<button onClick={Start}>start</button>
<button onClick={Stop}>stop</button>
</div>
</div>
);
}
2
Answers
Write the logic to set timerOn state to false in a useState like below and make other adjustments related to this state if needed:
Create a state for the
isCompleted
value, compute the initial value from the initialcolors
state valueUse a
useEffect
hook to compute and update theisCompleted
state when thecolors
state updatesUse a
useEffect
hook to end the timer when theisCompleted
state is trueFull Component Example: