i want to set a state in react in a loop going from 0 to “last step” but the state has to be set with a delay in other words:
-click a button, after the delay the state is set to 0, again after the delay the state is set to previous state +1, once the state is equal to “last step” end.
i tried this but is not working
const handleReplay = () => { setTimeout(() => { for (let i = 0; i < lastStep; i++) { setStepNumber(i) } }, 500); };
Advertisement
Answer
If the button starts a counter from 0
to lastStep
, then you need more than useState
:
Solution 1: states and effect
import { useEffect, useState } from "react"; // global constants: const INIT_STEP = -1; const LAST_STEP = 9; // for steps 0...9 (10 steps) const DELAY = 1000; export default () => { // not active by default const [active, setActive] = useState(false); const [step, setStep] = useState(INIT_STEP); useEffect(() => { // if we are active, then start the incrementing timer if (active) { // set the internal step state let currentStep = INIT_STEP; // create an interval which will increment the step const timer = setInterval(() => { if (currentStep < LAST_STEP) { currentStep = currentStep + 1; setStep(currentStep); } else { // stop here because we have reached the end of steps setActive(false); } }, DELAY); // will be called when active is set to false return () => clearInterval(timer); } }, [active]); // external control const handleButtonStart = () => { setStep(INIT_STEP); setActive(true); }; const handleButtonStop = () => setActive(false); return ( <div> <h3>Solution 1: states and effect!</h3> <div> {active ? (step > INIT_STEP ? `Step ${step}` : "Pending") : "Stopped"} </div> <button onClick={handleButtonStart}>Start</button> <button onClick={handleButtonStop}>Stop</button> </div> ); };
Solution 2: reducer
import { useEffect, useReducer } from "react"; const INIT_STEP = -1; const LAST_STEP = 9; // for steps 0...9 (10 steps) const DELAY = 1000; const INIT_STATE = { step: INIT_STEP, active: false }; const counterReducer = (state, action) => { if (action.type === "start") { return { step: INIT_STEP, active: true }; } else if (action.type === "stop") { return { ...state, active: false }; } else if (action.type === "next" && state.active) { if (state.step < LAST_STEP) { return { ...state, step: state.step + 1 }; } else { return { ...state, active: false }; } } else { return state; // no change } }; export default () => { const [{ step, active }, dispatch] = useReducer(counterReducer, INIT_STATE); useEffect(() => { if (active) { const timer = setInterval(() => { dispatch({ type: "next" }); }, DELAY); return () => clearInterval(timer); } }, [active]); const handleButtonStart = () => dispatch({ type: "start" }); const handleButtonStop = () => dispatch({ type: "stop" }); return ( <div> <h3>Solution 2: reducer!</h3> <div> {active ? (step > INIT_STEP ? `Step ${step}` : "Pending") : "Stopped"} </div> <button onClick={handleButtonStart}>Start</button> <button onClick={handleButtonStop}>Stop</button> </div> ); };
The code can be tested here.