React setInterval question (using setState inside another setState problem) - javascript

I'm new to reactjs; I encountered this problem while studying about useState. I'm trying to decrease the value of the second state when the first state decreases to 0, and the iteration will run until both states are 0. But the second state always decreases by 2, which makes me confused.
This is my code:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [firstCount,setFirstCount]=useState(10);
const [secondCount,setSecondCount] = useState(5);
function decreaseCount(){
const interval= setInterval(()=>{
setFirstCount((prevFirstCount)=>{
if(prevFirstCount>0){
return prevFirstCount-1;
}
else{
setSecondCount((prevSecondCount)=>{
if(prevSecondCount>0){
return prevSecondCount-1
}
else{
clearInterval(interval);
return prevFirstCount
}
})
return 10;
}
})
},1000)
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={(decreaseCount)}>Decrease Count</button>
</div>
);
}
codesandbox link: https://codesandbox.io/s/interval-setcountprob-plpzl?file=/src/App.js:0-835
I'd really appreciate if someone can help me out.

It's because the callback you pass to setFirstCount must be pure, but you violated that contract by trying to use it to mutate secondCount. You can correctly implement this dependency with useRef and useEffect:
export default function App() {
const [firstCount, setFirstCount] = useState(0);
const [secondCount, setSecondCount] = useState(6);
const firstCountRef = useRef(firstCount);
const secondCountRef = useRef(secondCount);
firstCountRef.current = firstCount;
secondCountRef.current = secondCount;
function decreaseCount() {
const interval = setInterval(() => {
if (secondCountRef.current === 0) {
clearInterval(interval);
return;
}
const { current } = firstCountRef;
setFirstCount(prev => (prev + 9) % 10);
setSecondCount(prev => current > 0 ? prev : (prev + 9) % 10);
}, 1000);
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={decreaseCount}>Decrease Count</button>
</div>
);
}
However, it might be easier to use a single state and compute the counts from that:
export default function App() {
const [count, setCount] = useState(60);
const countRef = useRef(count);
const firstCount = count % 10;
const secondCount = Math.floor(count / 10);
countRef.current = count;
function decreaseCount() {
const interval = setInterval(() => {
if (countRef.current === 0) {
clearInterval(interval);
return;
}
setCount(prev => prev - 1);
}, 1000);
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={decreaseCount}>Decrease Count</button>
</div>
);
}

I solved the issue this way :
const [firstCount, setFirstCount] = useState(10);
const [secondCount, setSecondCount] = useState(5);
const handleDecrease = () => {
setInterval(() => {
setFirstCount((prev) => {
if (prev > 0) {
return prev - 1;
}
if (prev === 0) {
return prev + 10;
}
});
}, 1000);
};
React.useEffect(() => {
if (firstCount === 0) {
setSecondCount((prev) => {
if (prev === 0) {
setFirstCount((firstPrev) => firstPrev + 10);
return prev + 5;
} else {
return prev - 1;
}
});
}
}, [firstCount]);
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={handleDecrease}>Decrease Count</button>
</div>
);

You shouldn't declare functions like this:
function decreaseCount(){
...
Instead you should use useCallback:
const decreaseCount = useCallback(() => {
//your code here
}[firstCount, secondCount]) //dependency array
You should read more about hooks: https://reactjs.org/docs/hooks-intro.html

Related

setTimout with pause/resume counter not updating on render

I would like to setup a counter which can be paused as well as resumed in React.js. But whatever I have tried so far is working the functionality part (pause/resume is working) but it's not updating the counter on render. Below is my code:
const ProgressBar = (props) => {
const [isPlay, setisPlay] = useState(false);
const [progressText, setProgressText] = useState(
props.duration ? props.duration : 20
);
var elapsed,
secondsLeft = 20;
const timer = () => {
// setInterval for every second
var countdown = setInterval(() => {
// if allowed time is used up, clear interval
if (secondsLeft < 0) {
clearInterval(countdown);
return;
}
// if paused, record elapsed time and return
if (isPlay === true) {
elapsed = secondsLeft;
return;
}
// decrement seconds left
secondsLeft--;
console.warn(secondsLeft);
}, 1000);
};
timer();
const stopProgress = () => {
setisPlay(!isPlay);
if (isPlay === false) {
secondsLeft = elapsed;
}
};
return (
<>
<p>{secondsLeft}</p>
</>
);
};
export default ProgressBar;
I have tried React.js state, global var type, global let type, react ref so far to make the variable global but none of them worked..
So basically why does your example not work?
Your secondsLeft variable not connected to your JSX. So each time your component rerendered it creates a new secondsLeft variable with a value of 20 (Because rerendering is simply the execution of your function that returns JSX)
How to make your variable values persist - useState or useReducer hook for react functional component or state for class based one. So react will store all the values for you for the next rerender cycle.
Second issue is React doesn't rerender your component, it just doesn't know when it should. So what causes rerendering of your component -
Props change
State change
Context change
adding/removing your component from the DOM
Maybe I missing some other cases
So example below works fine for me
import { useEffect, useState } from "react";
function App() {
const [pause, setPause] = useState(false);
const [secondsLeft, setSecondsLeft] = useState(20);
const timer = () => {
var countdown = setInterval(() => {
if (secondsLeft <= 0) {
clearInterval(countdown);
return;
}
if (pause === true) {
clearInterval(countdown);
return;
}
setSecondsLeft((sec) => sec - 1);
}, 1000);
return () => {
clearInterval(countdown);
};
};
useEffect(timer, [secondsLeft, pause]);
const pauseTimer = () => {
setPause((pause) => !pause);
};
return (
<div>
<span>Seconds Left</span>
<p>{secondsLeft}</p>
<button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
</div>
);
}
import React, { useState, useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
const [counter, setCounter] = useState(0);
const [isplayin, setIsPlaying] = useState(false);
const pause = () => {
setIsPlaying(false);
clearInterval(timer);
};
const reset = () => {
setIsPlaying(false);
setCounter(0);
clearInterval(timer);
};
const play = () => {
setIsPlaying(true);
timer = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
return (
<div className="App">
<p>Counter</p>
<h1>{counter}</h1>
{isplayin ? (
<>
<button onClick={() => pause()}>Pause</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<>
{counter > 0 ? (
<>
<button onClick={() => play()}>Resume</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<button onClick={() => play()}>Start</button>
)}
</>
)}
</div>
);
}
export default App;

How to useEffect pause setTimeout on handleMouseEnter event. Continue setTimeOout on handleMouseLeaveEvent?

I am trying to figure out how i can use the handleMouseEnter/Leave event to pause/continue the setTimeout. The rest of the code appears to be working fine for me.
function Education({ slides }) {
const [current, setCurrent] = useState(0);
const length = slides.length;
const timeout = useRef(null);
const [isHovering, setIsHovering] = useState(false);
useEffect(() => {
const nextSlide = () => {
setCurrent((current) => (current === length - 1 ? 0 : current + 1));
};
timeout.current = setTimeout(nextSlide, 3000);
return function () {
if (timeout.current) {
clearTimeout(timeout.current);
}
};
}, [current, length]);
function handleMouseEnter(e) {
setIsHovering(true);
console.log("is hovering");
}
function handleMouseLeave(e) {
setIsHovering(false);
console.log("not hovering");
}
}
Hey there you can do this by this simple implementation.
const {useEffect, useState, useRef} = React;
const Education = () => {
const slides = [1,2,3,4,5,6];
const [current, setCurrent] = useState(0);
const length = slides.length;
const timeout = useRef(null);
const [isHovering, setIsHovering] = useState(false);
useEffect(() => {
const nextSlide = () => {
setCurrent((current) => (current === length - 1 ? 0 : current + 1));
};
if ( !isHovering)
timeout.current = setTimeout(nextSlide, 2000);
return function () {
if (timeout.current) {
clearTimeout(timeout.current);
}
};
}, [current, length, isHovering]);
function handleMouseEnter(e) {
// stop the timeout function to be set
setIsHovering(true);
// clear any existing timeout functions
if ( timeout.current ){
clearTimeout(timeout.current);
}
}
function handleMouseLeave(e) {
// to trigger the useeffect function
setIsHovering(false);
}
return(
<div>
{
slides.map( (s, i) => {
if ( i === current){
return <div key={i} style={{padding:"2em", backgroundColor:"gray", fontSize:"2em"}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>{s}</div>
}
})
}
</div>
)
}
ReactDOM.render(<Education />, document.querySelector("#app"))
You can check out in JsFiddle

How can I have an image that changes every 5 seconds in react.js?

import React, { useEffect, useState } from "react";
import aa from '../imgs/aa.png'
import aa2 from '../imgs/aa2.JPG'
import aa3 from '../imgs/aa3.JPG'
import aa4 from '../imgs/aa4.JPG'
import './AnimatedGalery.css'
export default function () {
return (
<div>
</div>
)
}
I have no idea how to start with this. I basically want an image (that I can resize and give css properties) that changes every 5 seconds to another one of those 4 imported images I have here.
The idea: put all imported images in a list. Have a state variable for the current image that should be displayed. Set an intervall that executes every 5 seconds in a useEffect that sets the new state with a randomly picked image.
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentImage, setCurrentImage] = useState(null);
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentImage(images[Math.floor(Math.random() * items.length)]);
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={currentImage} />
</div>
)
}
If you want to have a rotation of your images, then I would just save the currentIndex and display its image:
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
if(currentIndex === images.length - 1) {
setCurrentIndex(0);
}
else {
setCurrentIndex(currentIndex + 1);
}
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={images[currentIndex]} />
</div>
)
}
You can define a function to get current image, and use setInterval() to change image every five seconds, which you can define inside useEffect when the component renders:
const getPicture = index => {
switch (index) {
case 1:
return "'../imgs/aa.png";
case 2:
...
default:
break;
}
};
const NUMBER_OF_PICTURES = 4;
export default function Menu() {
const [index, setIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setIndex(prevIndex => (index == NUMBER_OF_PICTURES ? 0 : prevIndex + 1));
}, 5000);
return () => {
/* cleanup */
clearInterval(timer);
};
/* on component render*/
}, []);
return (
<>
<img src={getPicture(index)} />
</>
);
}
you can add all the images name in a array like this :-
const temp =[aa,aa2,aa3,aa4]
const [counter,setCounter] = useState(0);
setInterval(function(){
counter === 4 ? setCounter(0) : setCounter(counter + 1);
}, 5000);
In tag you can use it like this
<img src=`{temp[counter]}` />

Stop game loop on conditional with React

I am unable to stop the loop after a conditional. I am able to stop the interval after a button click but unable to stop it after a conditional such as loop increments. This simple example tries to stop the interval loop after 5 loops.
Any solutions would be much appreciated!
import React, { useState } from 'react';
let gameLoop: any;
function App() {
const [loopCount, setLoopCount] = useState(0);
const [running, setRunning] = useState(true);
const gameLogic = () => {
console.log('Game logic!')
}
const loop = () => {
gameLogic();
setLoopCount(prev => {
const newCount = prev + 1;
console.log(newCount)
return newCount
});
// Stop the loop on a conditional
if(loopCount >= 5){
clearInterval(gameLoop)
}
}
const handleStartButtonClick = () => {
gameLoop = setInterval(loop, 1000)
setRunning(true);
}
const handleStopButtonClick = () => {
clearInterval(gameLoop);
setRunning(false);
}
const handleResetButtonClick = () => {
setLoopCount(0);
console.clear();
}
return (
<div className="App">
<div>
<button onClick={handleStartButtonClick}>Start</button>
<button onClick={handleStopButtonClick}>Stop</button>
<button onClick={handleResetButtonClick}>Reset</button>
</div>
</div>
);
}
export default App;
The solution is to put the conditional at the component level, not in the loop method.
import React, { useState } from 'react';
let gameLoop: any;
function App() {
const [loopCount, setLoopCount] = useState(0);
const [running, setRunning] = useState(true);
const gameLogic = () => {
console.log('Game logic!')
}
const loop = () => {
gameLogic();
setLoopCount(prev => {
const newCount = prev + 1;
console.log(newCount)
return newCount
});
}
//MOVE OUTSIDE GAME LOOP
// Stop the loop on a conditional
if(loopCount >= 5){
clearInterval(gameLoop)
}
const handleStartButtonClick = () => {
gameLoop = setInterval(loop, 1000)
setRunning(true);
}
const handleStopButtonClick = () => {
clearInterval(gameLoop);
setRunning(false);
}
const handleResetButtonClick = () => {
setLoopCount(0);
console.clear();
}
return (
<div className="App">
<div>
<button onClick={handleStartButtonClick}>Start</button>
<button onClick={handleStopButtonClick}>Stop</button>
<button onClick={handleResetButtonClick}>Reset</button>
</div>
</div>
);
}
export default App;

Increment counter continuously on mouse click in React.js

I am trying to implement counter in React.js which increments the value continuously on click but I am not getting the appropriate result, code works fine in plain html/js.
https://codesandbox.io/s/autumn-wood-fhsjx?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState(0);
const continuosIncerment = () => {
console.log(`Setting ${value}`);
setValue(value + 1);
timer = setTimeout(function() {
continuosIncerment();
}, 1000);
};
function timeoutClear() {
clearTimeout(timer);
}
return (
<div className="App">
<button
onMouseLeave={timeoutClear}
onMouseUp={timeoutClear}
onMouseDown={continuosIncerment}
>
Increment
</button>
<div>Value = {value} </div>
</div>
);
}
You can use setInterval, useRef and callback approach to update state to solve your issue.
Working demo
Code snippet
export default function App() {
const [value, setValue] = useState(0);
const timer = useRef(null);
const increment = () => {
timer.current = setInterval(() => setValue(prev => prev + 1), 500);
};
function timeoutClear() {
clearInterval(timer.current);
}
return (
<div className="App">
<button
onMouseLeave={timeoutClear}
onMouseUp={timeoutClear}
onMouseDown={increment}
>
Increment
</button>
<div>Value = {value} </div>
</div>
);
}
This is the code for a timer that I used on my app, maybe it can help you.
import React, { useState, useEffect } from "react";
import "./clock.css";
function Clock() {
const [isRunning, setIsRunning] = useState(false);
const [seconds, setSeconds] = useState(0);
const [minutes, setMinutes] = useState(0);
const [hours, setHours] = useState(0);
const toggleOn = () => {
setIsRunning(!isRunning);
};
const reset = () => {
setMinutes(0);
setSeconds(0);
setIsRunning(false);
setHours(0);
};
useEffect(() => {
let interval = null;
if (isRunning) {
interval = setInterval(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
if (seconds > 60) {
setMinutes((minutes) => minutes + 1);
setSeconds(0);
}
if (minutes > 60) {
setHours((hours) => {
return hours + 1;
});
setMinutes(0);
}
} else if (!isRunning && seconds !== 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isRunning, seconds]);
const formatedTime = () => {
let formatedSeconds = seconds < 10 ? `0${seconds}` : seconds;
let formatedMinutes = () => {
if (hours >= 1) {
if (minutes < 10) {
return `0${minutes}`;
} else return minutes;
} else if (hours < 1) {
return minutes;
}
};
let formatedHours = hours < 1 ? " " : `${hours}:`;
let formatedTime = `${formatedHours}${formatedMinutes()}:${formatedSeconds}`;
return formatedTime;
};
return (
<div className="clock">
<h1>{formatedTime()}</h1>
<div>
<button className="btn btn-primary m-1" onClick={toggleOn}>
{isRunning ? "Pause" : "Start"}
</button>
<button className="btn btn-primary m-1" onClick={reset}>
Reset
</button>
</div>
</div>
);
}
export default Clock;

Categories

Resources