UPDATE: OMG I'm using setTimeOut. But I still need an answer.
I made an application that accesses an array and outputs each of its elements after a certain period of time. When the array ends, execution stops.
There is a need to pause the execution. How can I do that?
const [isPaused, setIsPaused] = useState(false);
const togglePause = () => {
setIsPaused(!isPaused)
}
const soccData = data.player_positions; // cutting only IDs and positions from data
const [playerPosition, setPlayerPosition] = useState([]); // creating local state to work with IDs and positions
const getPlayerData = (arr) => { // function goes thru array of players and sets a new playerPosition on every step
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
setPlayerPosition(arr[i])
}, data.interval * (i + 1));
}
}
useEffect(() => {
getPlayerData(soccData)
return () => {
clearTimeout()
};
}, [soccData]);
return (
<div>{playerPosition}</div>
<p><button onClick={togglePause}></button></p>
)
I tried adding a condition if(!isPaused) to the function getPlayerData (and a dependency to useSeffect), but that didn't work.
Here's my code on codesandbox: https://codesandbox.io/s/youthful-paper-pvrq09
p.s. I found someone's code that allows to start/pause the execution: https://jsfiddle.net/thesyncoder/12q8r3ex/1/, but there's no ability to stop the execution when array ends.
The following works in your sandbox:
export default function App() {
const [isPaused, setIsPaused] = useState(false);
const [frame, setFrame] = useState(0);
const togglePause = () => {
setIsPaused(!isPaused);
};
const playerPosition = data.player_positions[frame];
useEffect(() => {
// do nothing if paused or the last frame is reached
if (isPaused || frame === data.player_positions.length - 1) {
return;
}
const to = setTimeout(() => {
setFrame((f) => f + 1);
}, data.interval);
return () => {
clearTimeout(to);
};
}, [frame, isPaused]);
return (
<div className="App">
<pre>{playerPosition}</pre>
<p>
<button onClick={togglePause}>{isPaused ? "play" : "pause"}</button>
</p>
</div>
);
}
As you can see, I removed the getPlayerData function. Instead, when the frame (which I called the index of the data we're currently at) or the isPaused variable changes, the effect is reran. It first (except for the very first execution) cleans up the still pending timeout (if there is one) and schedules a new one (if not paused and end is not reached) that increments the frame by one.
The main problem with your solution is the following:
const getPlayerData = (arr) => { // function goes thru array of players and sets a new playerPosition on every step
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
setPlayerPosition(arr[i])
}, data.interval * (i + 1));
}
}
This does not wait for the first timeout to occur, but instead schedules all frames in advance.
Therefore it is not pausable in a practical way (you could keep track of which timeout already ran, clear all of them when pausing and scheduling only the pending ones again once unpaused but that is not really practical)
setInterval() returns its id on execution. You can pass that id to clearInterval() to cancel it. Something like this might work:
let interval = 0;
interval = setInterval(() => {
// Your program logic
// ...
if(conditionTrueToCancelInterval) {
clearInterval(interval);
}
}, 1000)
import { ChangeEvent, useState } from 'react'
import { useInterval } from 'usehooks-ts'
export default function Component() {
// The counter
const [count, setCount] = useState<number>(0)
// Dynamic delay
const [delay, setDelay] = useState<number>(1000)
// ON/OFF
const [isPlaying, setPlaying] = useState<boolean>(false)
useInterval(
() => {
// Your custom logic here
setCount(count + 1)
},
// Delay in milliseconds or null to stop it
isPlaying ? delay : null,
)
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setDelay(Number(event.target.value))
}
return (
<>
<h1>{count}</h1>
<button onClick={() => setPlaying(!isPlaying)}>
{isPlaying ? 'pause' : 'play'}
</button>
<p>
<label htmlFor="delay">Delay: </label>
<input
type="number"
name="delay"
onChange={handleChange}
value={delay}
/>
</p>
</>
)
}
Edit:
I have an example on codepen showing the effects I want to implement. It is working, but I'm not sure if it is cleanly done to satisfy React 18's StrictMode.
https://codepen.io/yochess/pen/NWMOvrv?editors=0110
Question:
I have code where I have 2 div elements representing a left arrow and a right arrow.
When I hover over and click the arrow div element, I have css code that changes their styling using hover and active.
//file.css
.left-arrow-wrapper:active {
color: black;
}
.left-arrow-wrapper:hover{
background: rgb(254 226 226) !important;
}
.right-arrow-wrapper:active {
color: black;
}
.right-arrow-wrapper:hover{
background: rgb(254 226 226) !important;
}
I want to sort of emulate this effect with onkeydown.
For example, if the left arrow is clicked then setState is invoked on the left arrow's stylings. 0.1 seconds later using setTimeout, a 2nd setState is invoked on the left arrow and its styling would revert back to its original state.
As a result, useEffect is visited a few times. I want to make sure if a user is spamming the left arrow, then while its styling is changed, I want no effects to take place.
I am new to React and Hooks and the code above works on React 17, but when I change to React 18, the code is bugged. I'm assuming I am incorrectly implementing this effect. Is there a more proper way to accomplish this?
A side question is how do I properly clean up the setTimeouts on unmount? In the code below, I push all the setTimeouts into an array, and then set them to null once they are called. Then on unmount, I would return a function that clears the setTimeouts that are not null.
Here is the code:
import React, { useState, useEffect, useRef } from 'react'
import {
FaAngleLeft,
FaAngleRight,
} from "react-icons/fa"
const setStyles = {
defaultArrowStyle:{
backgroundColor: "lightgray",
borderStyle: "ridge"
},
clickedArrowStyle: {
backgroundColor: "rgb(254 226 226)",
borderStyle: "ridge"
},
}
const ArrowButtons = () => {
const [leftArrowStyle, setLeftArrowStyle] = useState(setStyles.defaultArrowStyle)
const [rightArrowStyle, setRightArrowStyle] = useState(setStyles.defaultArrowStyle)
const timeRef = useRef([])
const counterRef = useRef(0)
useEffect(() => {
window.addEventListener("keydown", handleKey)
return () => {
window.removeEventListener("keydown", handleKey);
}
}, [handleKey])
useEffect(() => {
if (rightArrowStyle.backgroundColor !== setStyles.clickedArrowStyle.backgroundColor) return
const counter = counterRef.current
const timer = setTimeout(() => cacheAndSetArrowStyle(setRightArrowStyle, counter), 100)
timeRef.current[counterRef.current++] = timer
}, [rightArrowStyle])
useEffect(() => {
if (leftArrowStyle.backgroundColor !== setStyles.clickedArrowStyle.backgroundColor) return
const counter = counterRef.current
const timer = setTimeout(() => cacheAndSetArrowStyle(setLeftArrowStyle, counter), 100)
timeRef.current[counterRef.current++] = timer
}, [leftArrowStyle])
useEffect(() => {
return () => {
// eslint-disable-next-line
timeRef.current.filter(timer => timer).forEach(timer => clearTimeout(timer))
}
}, [])
return (
<div className="row">
<div className="col-2 hand-icon text-center left-arrow-wrapper" style={leftArrowStyle} onClick={handleLeftClick}>
<FaAngleLeft className="left-arrow" />
</div>
<div className="col-2 hand-icon text-center right-arrow-wrapper" style={rightArrowStyle} onClick={handleRightClick}>
<FaAngleRight className="double-right-arrow" />
</div>
</div>
)
function handleKey(event) {
if (event.key === "ArrowRight") {
setRightArrowStyle(setStyles.clickedArrowStyle)
}
if (event.key === "ArrowLeft") {
setLeftArrowStyle(setStyles.clickedArrowStyle)
}
}
function cacheAndSetArrowStyle(setArrowStyle, counter) {
timeRef.current[counter] = null
setArrowStyle(setStyles.defaultArrowStyle)
}
}
export default React.memo(ArrowButtons)
I'm trying out the new React Hooks and have a Clock component with a time value which is supposed to increase every second. However, the value does not increase beyond one.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
The reason is because the callback passed into setInterval's closure only accesses the time variable in the first render, it doesn't have access to the new time value in the subsequent render because the useEffect() is not invoked the second time.
time always has the value of 0 within the setInterval callback.
Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.
Bonus: Alternative Approaches
Dan Abramov goes in-depth into the topic about using setInterval with hooks in his blog post and provides alternative ways around this issue. Highly recommend reading it!
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(prevTime => prevTime + 1); // <-- Change this line!
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
As others have pointed out, the problem is that useState is only called once (as deps = []) to set up the interval:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Then, every time setInterval ticks, it will actually call setTime(time + 1), but time will always hold the value it had initially when the setInterval callback (closure) was defined.
You can use the alternative form of useState's setter and provide a callback rather than the actual value you want to set (just like with setState):
setTime(prevTime => prevTime + 1);
But I would encourage you to create your own useInterval hook so that you can DRY and simplify your code by using setInterval declaratively, as Dan Abramov suggests here in Making setInterval Declarative with React Hooks:
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setInterval ticks again, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// interval will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the interval:
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
// Clear interval if the components is unmounted or the delay changes:
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
// Returns a ref to the interval ID in case you want to clear it manually:
return intervalRef;
}
const Clock = () => {
const [time, setTime] = React.useState(0);
const [isPaused, setPaused] = React.useState(false);
const intervalRef = useInterval(() => {
if (time < 10) {
setTime(time + 1);
} else {
window.clearInterval(intervalRef.current);
}
}, isPaused ? null : 1000);
return (<React.Fragment>
<button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
{ isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' }
</button>
<p>{ time.toString().padStart(2, '0') }/10 sec.</p>
<p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
</React.Fragment>);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
p + p {
margin-top: 8px;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Apart from producing simpler and cleaner code, this allows you to pause (and clear) the interval automatically by simply passing delay = null and also returns the interval ID, in case you want to cancel it yourself manually (that's not covered in Dan's posts).
Actually, this could also be improved so that it doesn't restart the delay when unpaused, but I guess for most uses cases this is good enough.
If you are looking for a similar answer for setTimeout rather than setInterval, check this out: https://stackoverflow.com/a/59274757/3723993.
You can also find declarative version of setTimeout and setInterval, useTimeout and useInterval, a few additional hooks written in TypeScript in https://www.npmjs.com/package/#swyg/corre.
useEffect function is evaluated only once on component mount when empty input list is provided.
An alternative to setInterval is to set new interval with setTimeout each time the state is updated:
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
The performance impact of setTimeout is insignificant and can be generally ignored. Unless the component is time-sensitive to the point where newly set timeouts cause undesirable effects, both setInterval and setTimeout approaches are acceptable.
useRef can solve this problem, here is a similar component which increase the counter in every 1000ms
import { useState, useEffect, useRef } from "react";
export default function App() {
const initalState = 0;
const [count, setCount] = useState(initalState);
const counterRef = useRef(initalState);
useEffect(() => {
counterRef.current = count;
})
useEffect(() => {
setInterval(() => {
setCount(counterRef.current + 1);
}, 1000);
}, []);
return (
<div className="App">
<h1>The current count is:</h1>
<h2>{count}</h2>
</div>
);
}
and i think this article will help you about using interval for react hooks
An alternative solution would be to use useReducer, as it will always be passed the current state.
function Clock() {
const [time, dispatch] = React.useReducer((state = 0, action) => {
if (action.type === 'add') return state + 1
return state
});
React.useEffect(() => {
const timer = window.setInterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((seconds) => {
if (seconds === 5) {
setSeconds(0);
return clearInterval(interval);
}
return (seconds += 1);
});
}, 1000);
}, []);
Note: This will help to update and reset the counter with useState hook. seconds will stop after 5 seconds. Because first change setSecond value then stop timer with updated seconds within setInterval. as useEffect run once.
This solutions dont work for me because i need to get the variable and do some stuff not just update it.
I get a workaround to get the updated value of the hook with a promise
Eg:
async function getCurrentHookValue(setHookFunction) {
return new Promise((resolve) => {
setHookFunction(prev => {
resolve(prev)
return prev;
})
})
}
With this i can get the value inside the setInterval function like this
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time => time + 1);// **set callback function here**
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
Somehow similar issue, but when working with a state value which is an Object and is not updating.
I had some issue with that so I hope this may help someone.
We need to pass the older object merged with the new one
const [data, setData] = useState({key1: "val", key2: "val"});
useEffect(() => {
setData(...data, {key2: "new val", newKey: "another new"}); // --> Pass old object
}, []);
Do as below it works fine.
const [count , setCount] = useState(0);
async function increment(count,value) {
await setCount(count => count + 1);
}
//call increment function
increment(count);
I copied the code from this blog. All credits to the owner. https://overreacted.io/making-setinterval-declarative-with-react-hooks/
The only thing is that I adapted this React code to React Native code so if you are a react native coder just copy this and adapt it to what you want. Is very easy to adapt it!
import React, {useState, useEffect, useRef} from "react";
import {Text} from 'react-native';
function Counter() {
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest function.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <Text>{count}</Text>;
}
export default Counter;
const [loop, setLoop] = useState(0);
useEffect(() => {
setInterval(() => setLoop(Math.random()), 5000);
}, []);
useEffect(() => {
// DO SOMETHING...
}, [loop])
For those looking for a minimalist solution for:
Stop interval after N seconds, and
Be able to reset it multiple times again on button click.
(I am not a React expert by any means my coworker asked to help out, I wrote this up and thought someone else might find it useful.)
const [disabled, setDisabled] = useState(true)
const [inter, setInter] = useState(null)
const [seconds, setSeconds] = useState(0)
const startCounting = () => {
setSeconds(0)
setDisabled(true)
setInter(window.setInterval(() => {
setSeconds(seconds => seconds + 1)
}, 1000))
}
useEffect(() => {
startCounting()
}, [])
useEffect(() => {
if (seconds >= 3) {
setDisabled(false)
clearInterval(inter)
}
}, [seconds])
return (<button style = {{fontSize:'64px'}}
onClick={startCounting}
disabled = {disabled}>{seconds}</button>)
}
Tell React re-render when time changed.opt out
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, [time]);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
I am trying to implement react counter. every 10 seconds I need to update label and progress bar. But in label I could able to display 1 to 6 in 60 seconds successfully. but in timer due to some issue even though it reaches 60 seconds progress bar percange showing 80% only.
timer logic
const [number, setNumber] = useState(0);
const [progBarVal, setProgBarValr] = useState(0);
useEffect(() => {
if (number >= 6) {
return;
}
const intervalID = setTimeout(() => {
setNumber((t) => t + 1);
setProgBarValr((t) => t + 10);
}, 1000);
return () => clearInterval(intervalID);
}, [number, progBarVal]);
with in the return statement
return{
<div > {number}/6 </div>
<Progress done={progBarVal} />
}
progress bar logic
import React, { useState } from 'react';
import './progress.scss';
const Progress = ({ done }) => {
const [style, setStyle] = useState({});
setTimeout(() => {
const newStyle = {
opacity: 1,
width: `${done}%`,
};
setStyle(newStyle);
}, 200);
return (
<div className='met-prog__progress'>
<div className='met-prog__progress-done' style={style}>
{done}%
</div>
</div>
);
};
export default Progress;
I am trying to do if number is 1 the progBarVal 10 like that.
someone help me to understand where it went wrong.
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.