I am having troubles fixing a bug basically I need to iterate over a list of buttons and apply an animation and on the next iteration I remove the animation from the previous element, however, when running the code the animation is started twice at the beginning and one element remains stuck with the animation applied.
The following is the code of the component:
import type { NextPage } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import { IoCheckmark, IoClose, IoHome, IoRefresh } from 'react-icons/io5'
import Page from '../components/page/Page'
import styles from '../styles/Play.module.css'
import { distance } from '../utils/distance'
import { randomInt, randomFloat } from '../utils/random'
function ShowSequence(props: any) {
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<any>();
useEffect(() => {
console.log(index)
if (index > 0) document.getElementById(sequence[index - 1])?.classList.toggle(styles.animate);
if (index < sequence.length) document.getElementById(sequence[index])?.classList.toggle(styles.animate);
else return clearInterval(timer);
setTimer(setTimeout(() => setIndex(index + 1), 3000));
}, [index]);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }}></button>
}) : null
}
</div>;
}
function DoTask(props: any) {
return <div>
</div>;
}
function ChooseSequence(props: any) {
const [sequence, setSequence] = useState(props.sequence);
const [index, setIndex] = useState(0);
const [timer, setTimer] = useState<any>();
const [buttonMap, setButtonMap] = useState<any>({});
console.log(sequence);
return <div className={styles.button}>
{
props.map ? props.map.map((button: any) => {
return <button key={button.buttonId} className={styles.button} id={button.buttonId} style={{ top: button.y + "px", left: button.x + "px", backgroundColor: button.color }} onClick={(e) => {
let correctSequence = sequence[index] === button.buttonId;
e.currentTarget.classList.toggle(correctSequence ? styles.correctButton : styles.wrongButton);
buttonMap[button.buttonId] = correctSequence ? <IoCheckmark size={20} color={"white"}></IoCheckmark> : <IoClose size={20} color={"white"}></IoClose>;
setButtonMap(buttonMap);
setIndex(index + 1);
}}>
{ (buttonMap[button.buttonId]) ? buttonMap[button.buttonId] : button.buttonId }
</button>
}) : null
}
</div>;
}
function Error(props: any) {
return <div className={styles.errorMenu}>
<h1>You lost!</h1>
<p>You reached level: {props.level}</p>
<div className={styles.container}>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
<div className={styles.item}></div>
</div>
<div className={styles.row}>
<button className={styles.retryButton} onClick={() => window.location.href = "/play"}><IoRefresh></IoRefresh></button>
<button className={styles.closeButton} onClick={() => window.location.href = "/"}><IoHome></IoHome></button>
</div>
</div>;
}
enum State {
SHOWSEQUENCE,
DOTASK,
CHOOSESEQUENCE,
ERROR
}
const Play: NextPage = () => {
let [state, setState] = useState<State>(State.SHOWSEQUENCE);
let [sequence, setSequence] = useState<number[]>([randomInt(1, 20), randomInt(1, 20), randomInt(1, 20), randomInt(1, 20)]);
let [map, setMap] = useState<any[]>();
let [level, setLevel] = useState(1);
let component;
useEffect(() => {
if (state === State.SHOWSEQUENCE) {
let newSequenceId = randomInt(1, 20);
setSequence((prevSequence: number[]) => [...prevSequence, newSequenceId])
}
}, [state]);
useEffect(() => {
let buttonIds = Array.from({ length: 20 }, (v, k) => k + 1);
const { innerWidth, innerHeight } = window;
let colors: string[] = ["#c0392b", "#e67e22", "#27ae60", "#8e44ad", "#2c3e50"];
let buttonMap: any[] = [];
let rows = buttonIds.length / 10;
let columns = rows > 0 ? buttonIds.length / rows : buttonIds.length;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
let color = colors[Math.floor(randomFloat() * colors.length)];
let x = innerWidth / columns * col + 100;
let y = innerHeight / rows * row + 100;
let offsetX = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerWidth / columns) - 100);
let offsetY = (randomFloat() < .5) ? -1 : 1 * randomFloat() * ((innerHeight / rows) - 100);
if (x + offsetX + 100 > innerWidth) offsetX -= ((x + offsetX) - innerWidth) + 100;
if (y + offsetY + 100 > innerHeight) offsetY -= ((y + offsetY) - innerHeight) + 100;
buttonMap.push({ buttonId: buttonIds[row * columns + col], x: x + offsetX, y: y + offsetY, color })
}
}
setMap(buttonMap);
}, [])
switch (state) {
case State.SHOWSEQUENCE:
component = <ShowSequence map={map} sequence={sequence} changeState={() => setState(State.DOTASK)}></ShowSequence>;
break;
case State.DOTASK:
component = <DoTask changeState={() => setState(State.CHOOSESEQUENCE)} onError={() => setState(State.ERROR)}></DoTask>
break;
case State.CHOOSESEQUENCE:
component = <ChooseSequence map={map} sequence={sequence} changeState={() => setState(State.SHOWSEQUENCE)} onError={() => setState(State.ERROR)}></ChooseSequence>
break;
}
return (
<Page color="blue">
{ state === State.ERROR ? <Error level={level}></Error> : null }
{component}
</Page>
)
}
export default Play
Here is a codesandbox.