Skip to content
Advertisement

How to set the state in react with a delay in a for loop

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.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement