OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

Stopping a timer in React by meeting a condition (instead of with a "stop" button)

  • Thread starter Thread starter user24713855
  • Start date Start date
U

user24713855

Guest
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?

Matching Game

Some of the code below:

("GroupOfSquares" component where I have the majority of the logic for this game--receives "colorsArray" from app.js)

Code:
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:

Code:
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>
  );
}

<p>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.</p>
<p>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).</p>
<p>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.</p>
<p>Can anyone point me in the right direction of how I should be looking at this?</p>
<p><a href="https://codesandbox.io/p/sandbox/match-game-ss2pgg" rel="nofollow noreferrer">Matching Game</a></p>
<p>Some of the code below:</p>
<p>("GroupOfSquares" component where I have the majority of the logic for this game--receives "colorsArray" from app.js)</p>
<pre><code>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 = 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>
);
}
</code></pre>
<p>Stopwatch component:</p>
<pre><code>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>
);
}
</code></pre>
 
Top