Why setSlideCount not working as expected? [duplicate] - javascript

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 2 years ago.
import React, {useState} from 'react';
function Slides({slides}) {
const [slideCount, setSlideCount] = useState(0);
const [slide, setSlide] = useState(slides[0]);
const [restartDisable, setRestartDisable] = useState(true);
const [previousDisable, setPreviousDisable] = useState(true);
const [nextDisable, setNextDisable] = useState(false);
const restartClick = () => {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
setSlideCount(0);
console.log("restartCLick ",slideCount);
}
const previousClick = () => {
setSlideCount(prevCount => prevCount - 1);
if (slideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[slideCount]);
}
console.log("previousCLick ",slideCount);
}
const nextClick = () => {
let newSlideCount = slideCount
newSlideCount++
console.log(newSlideCount)
setSlideCount(newSlideCount);
if (slideCount === (slides.length - 1)) {
setNextDisable(false);
setSlideCount(prevCount => prevCount + 1);
setSlide(slides[slideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[slideCount]);
}
console.log("nextCLick ",slideCount);
}
return (
<div>
<div id="navigation" className="text-center">
<button data-testid="button-restart" className="small outlined" disabled={restartDisable} onClick={()=>restartClick()}>Restart</button>
<button data-testid="button-prev" className="small" disabled={previousDisable} onClick={()=>previousClick()}>Prev</button>
<button data-testid="button-next" className="small" disabled={nextDisable} onClick={()=>nextClick()}>Next</button>
</div>
<div id="slide" className="card text-center">
<h1 data-testid="title">{slide.title}</h1>
<p data-testid="text">{slide.text}</p>
</div>
</div>
);
}
export default Slides;
The setSlideCount() is not setting the slideCount as expected, its incrementing late.
Whenever I click nextSlide the increment is shown in the react developer tools but the value remains same of the slideCount. Same thing applies for previousClick button also. But for restart button it works properly in setting to 0 but for the next button clicks and previous button clicks the slideCount value is not updating as expected, please help in setting slideCount value.

That's because setState is an asynchronous operation and won't update the state immidiately.
Here is the changes you need to make in your funtions:
const previousClick = () => {
setSlideCount((prevCount) => {
const newSlideCount = prevCount - 1;
if (newSlideCount === 0) {
setRestartDisable(true);
setPreviousDisable(true);
setNextDisable(false);
setSlide(slides[0]);
} else {
setSlide(slides[newSlideCount]);
}
console.log("previousCLick ", newSlideCount);
return newSlideCount;
});
};
const nextClick = () => {
setSlideCount((prevValue) => {
const newSlideCount = prevValue + 1;
if (newSlideCount === slides.length - 1) {
setNextDisable(false);
setSlideCount((prevCount) => prevCount + 1);
setSlide(slides[newSlideCount]);
} else {
setRestartDisable(false);
setPreviousDisable(false);
setSlide(slides[newSlideCount]);
}
console.log("nextCLick ", newSlideCount);
return newSlideCount;
});
};

Related

React iterating over list and applying animation with delay between each iteration

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.
Instead of querying the DOM, an anti-pattern in React, you should add the appropriate classname when mapping the data. This avoids the DOM mutations and handles the conditional logic for adding and removing the "animate" class.
Use a functional state update to increment the index.
I suggest using a React ref to hold a reference to the interval timer so it's not triggering additional rerenders
Return a cleanup function to clear any running timers when necessary, i.e. when the component unmounts.
Code:
function ShowSequence(props) {
const [index, setIndex] = useState(0);
const timerRef = useRef();
useEffect(() => {
console.log(index);
timerRef.current = setTimeout(() => setIndex((index) => index + 1), 3000);
return () => {
clearTimeout(timerRef.current);
};
}, [index]);
return (
<div className={styles.button}>
{props.map?.map((button, i) => {
return (
<button
key={button.buttonId}
className={[styles.button, i === index ? styles.animate : null]
.filter(Boolean)
.join(" ")}
id={button.buttonId}
style={{
top: button.y + "px",
left: button.x + "px",
backgroundColor: button.color
}}
></button>
);
})}
</div>
);
}
To cleanup the timer in the useEffect, you must return a function.
The previous element index is 1 less the current index for non-zero indexes or the last element in the array of buttons when the current index is the first item.
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
Also, you need to check if the previous element has the animation class before toggling the class. This takes care of the first time the animation starts to run (the previous element would not have the animation class).
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
Altogether, your effect would be along these lines:
const [index, setIndex] = useState(0);
const [sequence, setSequence] = useState(props.sequence);
const [timer, setTimer] = useState<number>();
useEffect(() => {
const prevElIndex = index == 0 ? props.map.length - 1 : index - 1;
if (
document
.getElementById(sequence[prevElIndex])
?.classList.contains(styles.animate)
) {
document
.getElementById(sequence[prevElIndex])
?.classList.toggle(styles.animate);
}
document.getElementById(sequence[index])?.classList.toggle(styles.animate);
setTimer(setTimeout(() => setIndex((index + 1) % sequence.length), 3000));
return () => clearTimeout(timer);
}, [index]);
A working Stackblitz showing this in action.

What's wrong with my code, click event fires only once JavaScript?

The loop works only once and then nothing happens. I have three testimonials, and can go only once forward or backwords.Thanks for help!
const nextBtn = document.querySelector(".next-btn");
const prevBtn = document.querySelector(".prev-btn");
const testimonials = document.querySelectorAll(".testimonial");
let index = 0;
window.addEventListener("DOMContentLoaded", function () {
show(index);
});
function show(index) {
testimonials.forEach((testimonial) => {
testimonial.style.display = "none";
});
testimonials[index].style.display = "flex";
}
nextBtn.addEventListener("click", function () {
index++;
if (index > testimonials.length - 1) {
index = 0;
}
show(index);
});
prevBtn.addEventListener("click", function () {
index--;
if (index < 0) {
index = testimonials.length - 1;
}
show(index);
});
I would use a "hidden" class to hide the non-active testimonials instead of manipulating the element's style in-line. Also, your navigation logic can be simplified to a modulo operation.
The code your originally posted seemed to work out well, but it seems to cluttered with redundancy (code reuse). It also lacks structural flow (readability).
const
modulo = (n, m) => (m + n) % m,
moduloWithOffset = (n, m, o) => modulo(n + o, m);
const
nextBtn = document.querySelector('.next-btn'),
prevBtn = document.querySelector('.prev-btn'),
testimonials = document.querySelectorAll('.testimonial');
let index = 0;
const show = (index) => {
testimonials.forEach((testimonial, currIndex) => {
testimonial.classList.toggle('hidden', currIndex !== index)
});
}
const navigate = (amount) => {
index = moduloWithOffset(index, testimonials.length, amount);
show(index);
}
// Create handlers
const onLoad = (e) => show(index);
const onPrevClick = (e) => navigate(-1);
const onNextClick = (e) => navigate(1);
// Add handlers
window.addEventListener('DOMContentLoaded', onLoad);
nextBtn.addEventListener('click', onNextClick);
prevBtn.addEventListener('click', onPrevClick);
.testimonial {
display: flex;
}
.testimonial.hidden {
display: none;
}
<div>
<button class="prev-btn">Prev</button>
<button class="next-btn">Next</button>
</div>
<div>
<div class="testimonial">A</div>
<div class="testimonial">B</div>
<div class="testimonial">C</div>
<div class="testimonial">D</div>
<div class="testimonial">E</div>
<div class="testimonial">F</div>
</div>

this is undefined when calling child function through a parent function

UPDATE: I have figured out a muuuuch simpler workaround by sing in the typescript file so the JS parent is no longer needed. ~facepalm~ Thanks for all your suggestions!
I am trying to get a button to trigger the function affTimer() inside the child function component but I keep getting the error "this is undefined" in relation to the function call. Here is the two code files:
affType.js
import React, {Component} from 'react';
import ReactPlayer from 'react-player'
import { Link } from 'react-router-dom';
import affirmationService from '../Services/requestService'
import affTrack from '../audio/inner.wav';
import warn from '../audio/warning.wav';
import Player from '../Player/Player';
import videoBG from '../videos/InnerStrength.mp4';
import Type from '../Type/Type.tsx';
import Button from "../customButton";
import {tXP} from '../Type/Type.tsx';
class affType extends Component {
constructor(props) {
super(props);
this.state = {character: undefined};
this.child = React.forwardRef();
this.startGame = this.startGame.bind(this);
}
async componentDidMount() {
const { match: { params } } = this.props;
//let affirmation_id = params.affirmation_id;
//let response = await affirmationService.getById(affirmation_id);
//this.setState({character: response.data});
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_blk_fast').style.display = 'none';
}, 1000);
}, 10);
}
spawnDialog() {
document.getElementById('overlay_1').style.display = 'block';
setTimeout(() => {
document.getElementById('overlay_1').style.opacity = 1;
}, 10);
}
destroyDialog() {
document.getElementById('overlay_1').style.opacity = 0;
setTimeout(() => {
document.getElementById('overlay_1').style.display = 'none';
}, 1000);
}
repeat() {
document.getElementById('overlay_2').style.opacity = 0;
document.querySelector('video').play();
setTimeout(() => {
document.getElementById('overlay_2').style.display = 'none';
}, 1000);
}
test_ended() {
document.getElementById('overlay_2').style.display = 'block';
setTimeout(() => {
document.getElementById('audio_end').play();
document.getElementById('overlay_2').style.opacity = 1;
}, 10);
}
startGame() {
var track = document.getElementById('aff');
track.play();
this.child.current.affTimer();
}
render() {
return (
<div>
<div className="contentplayer">
<audio id='aff'><source src={affTrack} /></audio>
<video autoPlay muted loop id="myVideo">
<source src={videoBG} type="video/mp4" />
</video>
<audio id="audio_end" src="/Audio/Inner Strength completed quest - play with completed quest prompt.wav"/>
</div>
<p>{tXP}</p>
<Button
border="none"
color="pink"
height = "200px"
onClick={this.startGame}
radius = "50%"
width = "200px"
children = "Start!"
/>
<Type ref={this.child}>
</Type>
<div className="aligntopright" onClick={() => {this.spawnDialog()}}>
<div className="backbtn-white"></div>
</div>
<div className="overlay_blk_fast" id="overlay_blk_fast"></div>
<div className="overlay" id="overlay_1">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Are you sure you would like to go back to the selection page?</h3>
<h2 className="no" onClick={() => {this.destroyDialog()}}>No</h2>
<Link to="/affirmation"><h2 className="yes">Yes</h2></Link>
</div>
</div>
</div>
<div className="overlay" id="overlay_2">
<div className="dialog">
<div className="dialogcontainer">
<img className="dialogbg"/>
<h3 className="dialogtext">Would you like to repeat this quest?</h3>
<Link to="/affirmation"><h2 className="no">Go back</h2></Link>
<h2 className="yes" onClick={() => {this.repeat()}}>Repeat</h2>
</div>
</div>
</div>
</div>
)
}
}
export default affType;
type.tsx
import React, {Component} from 'react';
import useTypingGame from "react-typing-game-hook";
import { textSpanContainsTextSpan } from 'typescript';
var xpM = 0;
var i = 0;
var err = 0;
var xp = 5;
var tXP = 0;
var addXP = 1;
var bonus = 0;
var bonusCounter = 0;
//var warnP = new Audio({warn});
//var affTrackP = new Audio('../audio/inner.wav');
function TypeF() {
let text_array = [
"There is strength and solidity within me",
"Courage is flooding through my veins",
"I possess strength within my heart",
"I am leading the charge with courage, and a vigorous resolution",
"There is a force inside me that is unbelievably powerful",
"There is a brave, radiant spirit inside me",
"I am a tall tree, with thick and strong roots",
"I was born for this",
"There is a divinity within",
"I am a force of nature",
"I possess the mental fortitude of those who climb the highest peaks",
"I was born with a determined spirit",
"There is an intensity in my eyes"
];
let text = text_array[i];
const {
states: {
charsState,
length,
currIndex,
currChar,
correctChar,
errorChar,
phase,
startTime,
endTime
},
actions: { insertTyping, resetTyping, deleteTyping }
} = useTypingGame(text);
const handleKey = (key: any) => {
if (key === "Escape") {
resetTyping();
} else if (key === "Backspace") {
deleteTyping(false);
} else if (key.length === 1) {
insertTyping(key);
}
};
if (currIndex + 1 === length) {
xpM = xpM + 1;
bonusCounter = bonusCounter + 1;
err = err + errorChar;
addXP = ((xp * correctChar) - (err * 2)) * xpM;
if (err > correctChar) {
addXP = correctChar * 3;
}
tXP = tXP + addXP;
if (bonusCounter >= 5) {
bonus = bonus + 1;
bonusCounter = 0;
}
resetTyping();
}
var tmr;
var cd = 18;
function affTimer() {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
function tock() {
if (cd > 0) {
cd = cd - 1;
console.log(cd);
}
else if (cd <= 0) {
if (i < text_array.length) {
i = i + 1;
cd = 18;
resetTyping();
}
else {
i = text_array.length;
}
}
}
return (
<div className='container'>
<div
className="typing-test"
id="start"
onKeyDown={(e) => {
handleKey(e.key);
e.preventDefault();
}
}
tabIndex={0}
>
{text.split("").map((char: string, index: number) => {
let state = charsState[index];
let color = state === 0 ? "white" : state === 1 ? "green" : "red";
return (
<span
key={char + index}
style={{ color }}
className={currIndex + 1 === index ? "curr-letter" : ""}
>
{char}
</span>
);
})}
</div>
<h2 className='debug'> TIMER: {cd}, I: {i}, ERRORS: {err}, MULTIPLIER: {xpM}, Type XP: {correctChar * xp}, CurrXP: {correctChar * xp * xpM} XPTotal: {tXP} bonusCounter: {bonusCounter}, BONUS: {bonus}</h2>
</div>
);
}
export {tXP};
export default TypeF;
Any help would be amazing, I have been stuck on this for 2 days and it is the last bit I need to complete so I can move to the next phase.
Your child component is a function component. You can't get a ref to an instance of a function component, because there is no instance. If you really need to use a function component and also expose some custom object as a ref, then you can use the useImperativeHandle hook plus forwardRef to define what the parent component should receive on its ref. For example:
const Type = React.forwardRef((props, ref) => {
// ...
useImperativeHandle(ref, () => {
// The following object is what will get assigned to the
// parent component's this.child.current
return {
afftimer: function () {
tmr = setInterval(tock, 1000);
if (i >= text_array.length) {
clearInterval(tmr);
}
}
}
});
// ...
})
But useImperativeHandle is not a common thing to use. There is likely a more standard way to solve your problem. The normal way for a parent component to tell a child component what to do is with props, not with refs.

Count using useState doesnt update state until question 6 even when answered correctly react

im trying to create a quiz app in react using hooks. i want the answers to be clickable and once clicked the user will then move onto the next question. My problem is that, the score state isnt updating until question 6! i am making an API call to get the questions and answers using useEffect. i know that useState is asynchronous and thats why state doesnt update straight away, its just even if i answer the first 6 questions correctly, after question 6 the score is still showing 1. Does anyone have a way around this?
My API call using useEffect:
useEffect(() => {
axios.get("https://my-quiz-server.herokuapp.com/api/newq").then((res) => {
const allQuestions = res.data;
setResult([allQuestions]);
})
.catch((error) => {
console.log(error);
});
}, [setResult]);
My Quiz component:
import React, {
useState,
useContext,
useEffect
} from "react";
import {
QuizContext
} from "../Helpers/context";
const MainQuiz = () => {
const {
score,
setScore,
result
} = useContext(QuizContext);
const [currentQuestion, setCurrentQuestion] = useState(0);
const [optionChosen, setOptionChosen] = useState("");
console.log(optionChosen);
console.log(result);
const nextQuestion = (correctAnswer) => {
if (optionChosen === correctAnswer) {
setScore((score) => score + 1);
}
setCurrentQuestion((currentQuestion) => currentQuestion + 1);
};
useEffect(() => {
console.log("score updated", score);
}, [score]);
return ( <
div className = "quiz" > {
result.map((question, index) => {
if (currentQuestion < question.allQuiz.length) {
return ( <
h3 key = {
index
} > {
question.allQuiz[currentQuestion].q_prompt
} < /h3>
);
} else {
return null;
}
})
} <
div className = "answer__container" > {
result.map((answers, index) => {
if (currentQuestion < answers.allQuiz.length) {
return ( <
div className = "answer__options" > {
" "
} <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("a");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].a
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("b");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].b
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("c");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].c
} <
/button> <
button className = "question__choices"
onClick = {
() => {
setOptionChosen("d");
nextQuestion(
result[0].allQuiz[currentQuestion].q_correctAnswer
);
}
} >
{
answers.allQuiz[currentQuestion].d
} <
/button> <
/div>
);
} else {
return null;
}
})
} <
/div> <
span > {
score
} < /span> <
/div>
);
};
export default MainQuiz;

Can't clear the setInterval

I want to clear the interval when stop button is clicked it seems the clearInterval is not working there as expected. The timer doesn't stop when handleStop function is triggered or when handleReset id triggered
Here's what I am trying to do:
import React from "react"
import useApp from "../App"
import Interact from "./Interact";
const Timer = () => {
let timer;
const {millisec , setMillisec , sec , setSec , min , setMin , hr , setHr} = useApp();
const handleStart = () => {
timer = setInterval(() => {
setMillisec((prev) => {
if (prev === 100) {
setSec((prevB) =>{
if(prevB === 60){
setMin(prevC => {
if(prevC === 60){
setHr(prevD => prevD + 1)
return 0
}
return prevC + 1
})
return 0
}
return prevB + 1
});
return 0;
}
return prev + 1;
});
}, 10);
};
const handleStop = () => {
clearInterval(timer)
}
const handleReset = () => {
clearInterval(timer)
setMillisec(0)
setSec(0)
setMin(0)
setHr(0)
}
return(
<>
<div className="parent">
<div className="main">
<h1 className="heading">Stop Watch</h1>
<div className="timer">
<div className ="hour">{hr}</div>
<div className ="min">{min}</div>
<div className ="sec">{sec}</div>
<div className ="millisec">{millisec}</div>
</div>
<Interact handleStart = {handleStart}
handleStop = {handleStop}
handleReset ={handleReset}
/>
</div>
</div>
</>
)
}
export default Timer
variable timer re-declare every time when Timer component rendered. Thus, the setInterval reference is reset.
You can declare timer at global scope, like
...
import Interact from "./Interact";
let timer;
const Timer = () => {
const {millisec , setMillisec , sec , setSec , min , setMin , hr , setHr} = useApp();
const handleStart = () => {
...
or can also use react-hooks to maintain setInterwal reference. like this
Pass your setInterval() to a clearInterval() when you need to break it.

Categories