react 25+5 clock for freecodecamp test fail - reactjs

I’m making the 25 + 5 clock for the freecodecamp certification but 2 test failled.
The test 10 and 11 for the #Timer are wrong.
" 25 + 5 clock has paused but time continued elapsing: expected ‘58’ to equal ‘59’ "
On my side, it’s working and you can test it yourself link to the deployed project here 1.
You can click the play and pause button as fast as you can, it work.
But the test not.
It’s for 2 days that I’m checking on stackoverflow, freecodecamp forum, google about this issue.
I did a lot of change but not possible to find the issue.
body component
import React from 'react';
import Compteur from './Compteur';
import Config from './Config';
import { useState, useEffect } from 'react';
const Body = () => {
const [sessionCounter, setSessionCounter] = useState(1500);
const [breakCounter, setBreakCounter] = useState(300);
const [counterScreenSession, setCounterScreenSession] = useState(sessionCounter);
const [play, setPlay] = useState(false);
const [session, setSession] = useState(true);
const handleSessionCounter = (e) => {
let number = e.currentTarget.dataset.session
if(number === "up"){
if(sessionCounter<3600){
return setSessionCounter(sessionCounter+60);
}else{
return sessionCounter;
}
}
else{
if(sessionCounter >= 120){
return setSessionCounter(sessionCounter-60);
}else{
return sessionCounter;
}
}
}
const handleBreakCounter = (e) => {
let number = e.currentTarget.dataset.breaker
if(number === "up"){
if(breakCounter<3600){
return setBreakCounter(breakCounter+60);
}else{
return breakCounter;
}
}
else{
if(breakCounter >= 120){
return setBreakCounter(breakCounter-60);
}else{
return breakCounter;
}
}
}
const handleClear = () => {
setPlay(false);
setSession(true);
setBreakCounter(300);
setSessionCounter(1500)
document.getElementById("beep").pause();
document.getElementById("beep").currentTime=0;
return setCounterScreenSession(1500);
}
const handleCounterScreen = () => {
setPlay(play=>!play);
}
useEffect(() => {
if(play && counterScreenSession>0){
const timer = window.setInterval(()=>{
setCounterScreenSession(counterScreenSession => counterScreenSession-1);
}, 1000);
return ()=>{
clearInterval(timer)
}
}
}, [play, counterScreenSession])
useEffect(() => {
if(counterScreenSession===0 && session){
document.getElementById("beep").play();
setCounterScreenSession(breakCounter);
setSession(!session);
}
if(counterScreenSession===0 && !session){
setCounterScreenSession(sessionCounter);
setSession(!session);
}
}, [counterScreenSession, session, breakCounter, sessionCounter])
useEffect(()=>{
return setCounterScreenSession(sessionCounter);
}, [sessionCounter, breakCounter])
const timeCounter = () =>{
let minutes = Math.floor(counterScreenSession/60);
let seconds = counterScreenSession%60;
if(minutes<10){
minutes = "0"+minutes;
}
if(seconds<10){
seconds = "0"+seconds;
}
return `${minutes}:${seconds}`;
}
return (
<div className="body">
<Config handleBreakCounter={handleBreakCounter} handleSessionCounter={handleSessionCounter}
sessionCounter={sessionCounter} breakCounter={breakCounter}/>
<Compteur counterScreenSession={counterScreenSession} play={play} handleCounterScreen=
{handleCounterScreen} handleClear={handleClear} session={session} sessionCounter={sessionCounter}
timeCounter={timeCounter} breakCounter={breakCounter}/>
</div>
);
};
export default Body;
Compteur component
import React from 'react';
import { AiFillPauseCircle, AiFillPlayCircle } from "react-icons/ai";
import {VscDebugRestart} from "react-icons/vsc";
import { CircularProgressbar } from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css';
const Compteur = ({counterScreenSession, play, handleCounterScreen, handleClear, session,
timeCounter,breakCounter,sessionCounter}) => {
return (
<div className={"compteur"} >
<div className="compteur__name" id="timer-label">{session? "Session" : "Break"}</div>
<CircularProgressbar
className="compteur__animation"
value={counterScreenSession}
minValue={0}
maxValue={session? sessionCounter:breakCounter }
counterClockwise="true"
styles={{
path:{
stroke: "#005479"
},
trail:{
stroke:"#A8223A"
}}
}
/>
<div className="compteur__time"
className={counterScreenSession<600 && counterScreenSession%60<5?"compteur__time
compteur__name--red" : "compteur__time" }id="time-left">
{
/*
counterScreenSession<600 && counterScreenSession%60<10 ?
"0"+Math.floor(counterScreenSession/60)+":0"+counterScreenSession%60:
counterScreenSession>599 && counterScreenSession%60<10 ?
Math.floor(counterScreenSession/60)+":0"+counterScreenSession%60:
counterScreenSession<600 && counterScreenSession%60>10 ?
"0"+Math.floor(counterScreenSession/60)+":"+counterScreenSession%60:
Math.floor(counterScreenSession/60)+":"+counterScreenSession%60
*/
timeCounter()
}
</div>
<audio id="beep" src="./sound/duke-game-over.mp3"></audio>
<div className="compteur__controler">
{
play === false ?<button className="compteur__controler__play" id="start_stop" onClick=
{handleCounterScreen} ><AiFillPlayCircle/></button>:<button className="compteur__controler__break"
onClick={handleCounterScreen}><AiFillPauseCircle/></button>
}
<button className="compteur__controler__clear" id="reset" onClick={handleClear}>
<VscDebugRestart/></button>
</div>
</div>
);
};
export default Compteur;
Link to the repo on github here.

I had the same problem and solved it using a class-based solution.
What I had was 53 - 55. It means there's a 2000ms (in your case is 1000ms) difference between the time the test case was paused and the time the test case replayed. The problem was because I chained the beep sound play, then setting the state (switching session-break), and clearing the interval (so it was serially firing three different functions). It was solved when I moved the beep sound play and switching session-break, all together within the function that invokes clearInterval (so they're all "fired together" in that function).
That chaining might've happened here:
if(counterScreenSession===0 && session){
document.getElementById("beep").play();
setCounterScreenSession(breakCounter);
setSession(!session);
}
if(counterScreenSession===0 && !session){
setCounterScreenSession(sessionCounter);
setSession(!session);
}
So if I were you, I probably would try to incorporate those beep play and setsession within the return function in the else statement here, together with the clearInterval. Not sure if it'll work out though, I'm not even sure it could be played that way, but it might be worth it to toy around with that idea.
if(play && counterScreenSession>0){
const timer = window.setInterval(()=>{
setCounterScreenSession(counterScreenSession => counterScreenSession-1);
}, 1000);
return ()=>{
clearInterval(timer)
}
}

Related

Audio is not working properly after playing multiple time in ReactJS

I'm new to React and trying to add some audio to a tenzies game.
The audio is getting weirder every time I click the roll button. And after clicking for a while, the sound is fading away. There is also a warning in the console: 'The AudioContext was not allowed to start.'
I can't find out what causing this issue. Please help!
I don't know how to run react code in StackOverflow. So I'm adding a link to the github repository and live site URL of this game.
Here is the full App.js component's code:
import React from 'react';
import Die from './Components/Die';
import { nanoid } from 'nanoid';
import Confetti from 'react-confetti';
import {Howl} from 'howler';
import winSound from './audio/win.mp3';
import rollSound from './audio/roll.mp3';
import holdSound from './audio/hold.mp3';
export default function App(){
const [dice, setDice] = React.useState(generateNewDice());
const [tenzies, setTenzies] = React.useState(false);
const [audio, setAudio] = React.useState(true);
const [rollCount, setRollCount] = React.useState(0);
const [timer, setTimer] = React.useState(0);
const [timerRunning, setTimerRunning] = React.useState(false);
function holdDieObject(){
return {
value: Math.floor(Math.random()*6) + 1,
isHeld: false,
id: nanoid()
}
}
function generateNewDice(){
let newDice= [];
for(let i=0; i<10; i++){
newDice.push(holdDieObject());
}
return newDice;
}
React.useEffect(()=>{ //Count time per 10 milliseconds when timer is running
let interval;
if(timerRunning){
interval = setInterval(() => {
setTimer((prevTime) => prevTime + 10)
}, 10)
}else{
clearInterval(interval);
}
return () => clearInterval(interval);
},[timerRunning])
React.useEffect(()=>{ //Check all the dice are matched or not
const someDiceHeld = dice.some(die => die.isHeld);
const allDiceHeld = dice.every(die => die.isHeld);
const firstDiceValue = dice[0].value;
const allSameValue = dice.every(die=> die.value === firstDiceValue);
if(someDiceHeld){
setTimerRunning(true);
}
if(allDiceHeld && allSameValue){
setTenzies(true);
// audio && victorySound.play(); // This brings up dependency warning. So moved it to the bottom
setTimerRunning(false)
}
},[dice])
const victorySound = new Howl({
src: [winSound]
})
if(tenzies){
audio && victorySound.play(); // Here
}
const rollDieSound = new Howl({
src: [rollSound]
})
const holdDieSound = new Howl({
src: [holdSound]
})
function holdDice(id){
audio && holdDieSound.play();
setDice(oldDice => oldDice.map(die =>{
return die.id===id ?
{
...die,
isHeld: !die.isHeld
} :
die;
}))
}
function rollDice(){
if(!tenzies){
setDice(oldDice => oldDice.map(die=>{
audio && rollDieSound.play();
return die.isHeld ? die : holdDieObject();
}))
setRollCount(prevCount => prevCount + 1);
}else{
setTenzies(false);
setDice(generateNewDice());
setRollCount(0);
setTimer(0);
}
}
function toggleMute(){
setAudio(prevState => !prevState);
}
function startNewGame(){
setTenzies(false);
setDice(generateNewDice());
}
const minutes = <span>{("0" + Math.floor((timer / 60000) % 60)).slice(-2)}</span>
const seconds = <span>{("0" + Math.floor((timer / 1000) % 60)).slice(-2)}</span>
const milliseconds = <span>{("0" + ((timer / 10) % 100)).slice(-2)}</span>
const dieElements = dice.map((die) => {
return <Die key={die.id}
value={die.value}
isHeld={die.isHeld}
holdDice={()=> holdDice(die.id)}
/>
})
return(
<div>
<main className="board">
<button onClick={toggleMute} className="mute-btn">{audio ? "🔉" : "🔇"}</button>
<h1>Tenzies</h1>
<p>Roll untill the dice are the same. Click each die to freeze it at its current value between rolls.</p>
<div className="die-container">
{dieElements}
</div>
<button onClick={rollDice}>Roll</button>
{tenzies && <div className="scoreboard">
<h2>Congratulations!</h2>
<p className='rollCount'>Rolled: {rollCount}</p>
<p className="rolltime">Time Taken: {minutes}:{seconds}:{milliseconds}</p>
<h3>Your Score: 4500</h3>
<button className='close' onClick={startNewGame}>New Game</button>
</div>}
</main>
{tenzies && <Confetti className="confetti" recycle={false} />}
</div>
)
}

MediaSession Play/Pause button does not work in Chrome

I'm building a media controller application that controls a media player running in a remote process, and trying to use the MediaSession API to facilitate media key control. An audio element that is nearly silent is used to establish the media session, and after a few seconds it is paused indefinitely.
This works well in Firefox, but in Chrome-based browsers (desktop and mobile) the Play/Pause button does not change state and ultimately stops working after a few seconds. The Next/Previous track buttons work as expected.
What do I need to do to make the Play/Pause media session controls work in Chrome-based browsers?
React app to reproduce the issue:
import "./styles.css";
import React from "react";
export default function App() {
return (
<div className="App">
<h1>MediaSession demo</h1>
<Player />
</div>
);
}
function Player() {
const playctl = usePlayer();
if (playctl.state === "stopped") {
return <button onClick={playctl.playPause}>Begin</button>;
}
return (
<>
<p>Use media session notification to control player state.</p>
<MediaSession playctl={playctl} />
<p>Player state: {playctl.state}</p>
<p>Track: {playctl.track}</p>
</>
);
}
function usePlayer() {
const [state, setState] = React.useState("stopped");
const [track, setTrack] = React.useState(1);
let playing = state === "playing";
return {
playPause: () => {
playing = !playing;
setState(playing ? "playing" : "paused");
},
nextTrack: () => {
setTrack(track < 5 ? track + 1 : 1);
},
prevTrack: () => {
setTrack(track > 1 ? track - 1 : 5);
},
state,
nextState: playing ? "Pause" : "Play",
playing,
track
};
}
const MediaSession = ({ playctl }) => {
const controls = useMediaControls();
React.useEffect(() => controls.update(playctl), [controls, playctl]);
return controls.audio;
};
function useMediaControls() {
const audiofile = require("./near-silence.mp3");
const hasSession = window.navigator && "mediaSession" in window.navigator;
const ref = React.useRef();
let shouldShow = true;
function showControls(audio) {
shouldShow = false;
audio.volume = 0.00001; // very low volume level
audio.play();
audio.currentTime = 0;
// pause before track ends so controls remain visible
setTimeout(() => audio.pause(), 5000);
}
function updateSession(playctl) {
const session = window.navigator.mediaSession;
session.playbackState = playctl.playing ? "playing" : "paused";
session.setActionHandler("pause", playctl.playPause);
session.setActionHandler("play", playctl.playPause);
session.setActionHandler("nexttrack", playctl.nextTrack);
session.setActionHandler("previoustrack", playctl.prevTrack);
}
function createApi() {
return {
audio: hasSession && <audio ref={ref} src={audiofile} />,
update: (playctl) => {
if (hasSession) {
const audio = ref.current;
shouldShow && audio && showControls(audio);
updateSession(playctl);
}
}
};
}
return React.useState(createApi)[0];
}
Code sandbox: https://codesandbox.io/s/mediasession-demo-r773i

Simple countdown - onClick button fires on render

I'm trying to build a very simple 5 second countdown in React and can't seem to get it to work. In the code below, the timer starts as soon as the app renders for the first time, and then once it gets past 0 it seems to flicker and break down.
Any suggestions?
import './styles/App.css';
import { useState } from 'react';
function App() {
const [ time, setTime ] = useState(5);
const startCountdown = setInterval(() => {
if (time < 0 ) {
clearInterval(startCountdown);
}
setTime(time - 1);
}, 1000);
return (
<div className="App">
<h1>{time}</h1>
<button onClick={startCountdown}>Start</button>
</div>
);
}
export default App;
You need to pass it as a function. Currently your assigning this as code block which will be executed straightway.
exp:
const startCountdown = () => setInterval(() => {
if (time < 0 ) {
clearInterval(startCountdown);
}
setTime(time - 1);
}, 1000);

Unexpected useEffect behaviour

I am making hangman game. When letter is clicked it should not be clickable any more.
Here' a simplified version on codesandbox:
https://codesandbox.io/s/intelligent-cori-6b80q
In the sandbox, when letter is clicked it changes to "CLICKED!". Everything seems well here.
But in my project I get strange behavior.
AvailableLetter.js
const AvailableLetter = (props) => {
const [clicked,setClicked]=useState(false);
const setStuff = () => {
setClicked(false); // If I delete this I get Error: Maximum update depth exceeded.
props.setSolved();
};
useEffect( setStuff,[clicked] );
if (clicked) // IF CLICKED REMAINS TRUE, EVERY LETTER GETS PLAYED.
{
if (props.play())
{
props.correct();
}
else
{
props.incorrect();
}
}
const disableLetter = () => {
setClicked(true);
};
let letter=span onClick={disableLetter}>{props.alphabet}</span>;
if(clicked) // CODE NEVER GETS HERE!!!!!!!!!!!!!!
{
letter = <span>{props.alphabet}</span>;
}
return (
<Ax> // Ax is a high order class, just a wrapper
{letter}
</Ax>
);
}
Letter remain clickable even after being clicked. This is not what I want.
Each letter is rendered by Letters.js which feeds in a-z and generates custom AvailableLetter.
const availableLetters = [ ...allLetters ].map(
(alphabet,i) => {
return (
<AvailableLetter setSolved={props.setSolved} play={()=>playHandler(alphabet)} correct={()=>props.correct(alphabet)} incorrect={()=>props.incorrect(alphabet)} solution={props.solution} key={i} alphabet={alphabet} />
);
}
);
So issues to be solved here are:
- Letters remain clickable even after a click
- If setClicked(false) is removed it causes an infinite loop
const setStuff = () => {
setClicked(false); // if removed causes infinite loop
props.setSolved();
};
All of this is strange because in the codesandbox I don't need to set clicked to false inside setEffect().
You can see all of the code here: https://github.com/gunitinug/hangmanerrors/tree/master/src
Please have a look at project code, it is not long.
You need to understand how useEffect works. Whenever the state changes, useEffect gets called and the handler (in your case setStuff) passed as a callback to useEffect gets executed.
So, setStuff keeps updating the state and useEffect keeps getting called continuously. Hence, you see the error Error: Maximum update depth exceeded.
Change your code as shown below:
const AvailableLetter = (props) => {
const [clicked, setClicked] = useState(false);
const setStuff = () => {
if(clicked) {
if (props.play()) {
props.correct();
}
else {
props.incorrect();
}
setClicked(false);
props.setSolved();
}
};
useEffect( setStuff, [clicked] );
return (
<Ax>
<button onClick={() => setClicked(true)} disabled={clicked}>{props.alphabet}</button>
</Ax>
);
}
As mentioned in the comment posted on your question, this might not be the best approach but my code should solve your problem.
It was a structural problem, I had to delegate handler functions and state to the parent component Letters.js
I got to make it work silky smooth :p
import React, {useState} from 'react';
import AvailableLetter from './AvailableLetter/AvailableLetter';
import DisabledLetter from './DisabledLetter/DisabledLetter';
import classes from './Letters.module.css';
const Letters = (props) => {
const [lettersMap, setLettersMap]=useState(
{
"a":false,"b":false,"c":false,"d":false,"e":false,"f":false,"g":false,"h":false,"i":false,"j":false,"k":false,"l":false,"m":false,"n":false,"o":false,"p":false,"q":false,"r":false,"s":false,"t":false,"u":false,"v":false,"w":false,"x":false,"y":false,"z":false
}
);
const updateClickedHandler = (letter) => {
setLettersMap(
{
...lettersMap,[letter]:true
}
);
};
const playHandler = (alphabet) => {
const solution = props.solution.split('');
console.log(solution);
if (solution.indexOf(alphabet)<0)
{
console.log('incorrect');
return false;
}
else
{
console.log('correct');
return true;
}
}
const renderedLetters = Object.keys(lettersMap).map(
(letter,i)=>{
if (!lettersMap[letter]) //letter is not yet clicked
{
return (
<AvailableLetter updateClicked={updateClickedHandler} setSolved={props.setSolved} play={()=>playHandler(letter)} correct={()=>props.correct(letter)} incorrect={()=>props.incorrect(letter)} solution={props.solution} key={i} alphabet={letter} />
)
}
else //letter is clicked
{
return (
<DisabledLetter alphabet={letter} />
)
}
}
);
return (
<div className={classes.Letters}>
<p>Solution: {props.solution}</p>
<div className={classes.AvailableLetters}>
{renderedLetters}
</div>
</div>
);
}
export default Letters;
and
import React from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';
const AvailableLetter = (props) => {
const setStuff = () => {
if (props.play()) {
props.correct();
}
else {
props.incorrect();
}
props.updateClicked(props.alphabet);
props.setSolved();
};
return (
<Ax>
<span className={classes.AvailableLetter} onClick={setStuff}>{props.alphabet}</span>
</Ax>
);
}
export default AvailableLetter;
and finally, I added another component called DisabledLetter.js
import React from 'react';
import classes from './DisabledLetter.module.css';
const DisabledLetter = (props) => {
return (
<span className={classes.DisabledLetter} >{props.alphabet}</span>
);
};
export default DisabledLetter;
You can view entire source here
https://github.com/gunitinug/hangmanclicked/tree/master/src
;)
Discovered an issue though, when game is solved I need to click one more letter for 'YOU WIN' message to display.
When I die 'GAME OVER' is displayed immediately.

React hangman game: clicked state not managed correctly producing unexpected behavior

Hello I have Letters.js which generates AvailableLetter for a-z.
import React, {useState} from 'react';
import AvailableLetter from './AvailableLetter/AvailableLetter';
import classes from './Letters.module.css';
const Letters = (props) => {
const [allLetters]=useState(
['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
);
const playHandler = (alphabet) => {
const solution = props.solution.split('');
console.log(solution);
if (solution.indexOf(alphabet)<0)
{
console.log('incorrect');
return false;
}
else
{
console.log('correct');
return true;
}
}
const availableLetters = [ ...allLetters ].map(
(alphabet,i) => {
return (
<AvailableLetter setSolved={props.setSolved} play={()=>playHandler(alphabet)} correct={()=>props.correct(alphabet)} incorrect={()=>props.incorrect(alphabet)} solution={props.solution} key={i} alphabet={alphabet} />
);
}
);
return (
<div className={classes.Letters}>
<p>Solution: {props.solution}</p>
<div className={classes.AvailableLetters}>
{availableLetters}
</div>
</div>
);
}
export default Letters;
I have AvailableLetter.js here and I want it to be unclickable after first time clicked.
import React, {useState, useEffect} from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';
const AvailableLetter = (props) => {
// const [show,setShow]=useState(true);
// const [clicked, setClicked]=useState(false);
// const [outcome,setOutcome]=useState(false);
const [clicked,setClicked]=useState(false);
// if (show)
// {
// setClicked(true);
// }
// const play = (alphabet) => {
// const solution = props.solution.split('');
// if (solution.indexOf(alphabet)<0)
// {
// return false;
// }
// else
// {
// return true;
// }
// }
const setStuff = () => {
// setShow(true);
setClicked(false);
props.setSolved();
};
useEffect( setStuff,[clicked] );
// useEffect( ()=>setShow(true),[show] );
// useEffect( ()=>props.setSolved(),[show] );
if (clicked) // STRANGELY THIS PART WORKS!!!
{
if (props.play())
{
props.correct();
// alert('correct');
}
else
{
props.incorrect();
// alert('wrong');
}
}
const attachedClasses = [classes.AvailableLetter];
const disableLetter = () => {
attachedClasses.push(classes.Disabled);
setClicked(true);
};
// const letter = <span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>;
let letter=null;
if (!clicked)
{
letter = <span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>;
}
else if(clicked) // CODE NEVER GETS HERE!!!!!!!!!!!!!!
{
letter = <span className={attachedClasses.join(' ')} >{props.alphabet}</span>;
}
return (
<Ax>
{letter}
</Ax>
);
}
export default AvailableLetter;
The CSS file for it is AvailableLetter.module.css:
.AvailableLetter
{
border: 1px solid black;
padding: 10px;
margin: 3px;
}
.AvailableLetter.Disabled
{
pointer-events: none;
background: #aaa;
}
It seems my logic inside AvailableLetter is correct, but it never reaches the else if (clicked) part and letters remain always clickable.
Inside AvailableLetter.js: If I use button instead:
<button disable={clicked} onClick={()=>setClicked(true)}>props.alphabet</button>
Strangely disable doesn't work even when setClicked(true).
But if I do
<button disable>props.alphabet</button>
Now it disables.
I appreciate your help!
Update:
Removing setClicked(false) from setStuff() gets error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Your combination of effects and hooks have created a feedback loop.
This button:
<span onClick={disableLetter} className={attachedClasses.join(' ')} >{props.alphabet}</span>
calls this function:
const disableLetter = () => {
attachedClasses.push(classes.Disabled);
setClicked(true);
};
which sets clicked to true. Once that happens, this effect runs:
const setStuff = () => {
// setShow(true);
setClicked(false);
props.setSolved();
};
useEffect( setStuff,[clicked] );
which immediately makes clicked == false again. Also worth noting that setStuff gets called a second time because clicked changed values, triggering the effect again. What is setStuff supposed to do in this context? Remove the call to setClicked(false) in that function and clicked should remain as true.
I'd highly recommend cleaning up your code so it's easier to follow. Logic like this:
if (!clicked) {
// not clicked
} else if (clicked) {
// clicked
}
could easily be described like this:
if (clicked) {
// clicked
} else {
// not clicked
}
By doing this, you'll save yourself a lot of headaches when debugging problems like the one you're having.
Maximum Update Depth Error
Based on your stack trace and code in the question/pastebin, you have another loop caused by this part:
if (clicked)
{
if (props.play())
{
props.correct();
// alert('correct');
}
else
{
props.incorrect(); // <- this is your trigger
// alert('wrong');
}
}
You should move this code into your setStuff function so it's called only once by the effect.
I'd also suggest re-thinking your structure here, so you (and others) can follow what you're doing better. Stack traces will help you with any further errors you get so you can follow the source of more loops you might encounter.

Resources