Reputation: 115
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);
};
Upvotes: 0
Views: 646
Reputation: 53616
If the button starts a counter from 0
to lastStep
, then you need more than useState
:
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>
);
};
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.
Upvotes: 1
Reputation: 446
There is more to it, but first clue is you are waiting a timeout and then running a loop. Instead, you're going to wait a timeout and then increment state, repeat.
This is one way that should get it done.
const timeoutRef = useRef();
const [stepNumber, setStepNumber] = useState(undefined);
useEffect(() => {
return () => {
// On unmount, stop any current animation
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
}, []);
const onClick = useCallback(() => {
let iStepNumber = 0;
const incrementStepNumber = () => {
setStepNumber(iStepNumber++);
if (iStepNumber === 100 /* The last step */) {
timeoutRef.current = undefined;
return;
}
timeoutRef.current = setTimeout(() => {
incrementStepNumber();
}, 500);
};
timeoutRef.current = setTimeout(() => {
incrementStepNumber();
}, 500);
}, []);
Upvotes: 0
Reputation: 69
The for loop will run from start to end. if you only want to execute code on a click event then just use:
const [stepNumber, setStepNumber] = useState(0);
const handleReplay = () => {
setTimeout(() => {
if (stepNumber < lastStep) {
setStepNumber((prevNum) => prevNum + 1);
}
}, 500);
};
Upvotes: 0