Home > Net >  React Typescript: setTimeout in For Loop is not populating array consecutively, or at all
React Typescript: setTimeout in For Loop is not populating array consecutively, or at all

Time:12-28

I'm building a function that makes images of random cars animate across the screen, and I want to stagger the population of the "carsLeft" array with a setTimeout..(which I will ultimately randomize the delay time).

everything works until I try to use a setTimeout. With the code below, no cars get are shown. a Console.log shows that the "carsLeft" array does not populate. When I remove the setTimeout all the cars are shown (at once of course). I have tried IIDE still no luck. Been stuck on this one for awhile, Please Help!

function Traffic() {
  let carsLeft: any = [];
  const generateCarLeft = () => {
    for (let i = 0; i < 5; i  ) {
      setTimeout(() => {
        carsLeft.push(
          <CarLeft key={i} className="car__left">
            <img
              src={carListDay[Math.floor(Math.random() * carListDay.length)]}
              alt=""
            />
          </CarLeft>
        );
      }, 3000);
    }
  };

  generateCarLeft();
  return <div className="traffic__container">{carsLeft}</div>;
}

export default Traffic;

CodePudding user response:

If you want to generate elements through the loop and happen after the component is mounted is using React.useEffect hook, for example

React.useEffect(() => {
  generateCarsLeft()
}, [])

and also using the carsLeft as a state will solve the problem.

CodePudding user response:

If do without state and setTimeout it'll work because before first render(mount) all the data is available.

but if you use on first render list is empty and after setTimeout it'll update variable but react does not know that variable is updated you have to use the state to solve this issue.

function Traffic() {
  const [carsLeft, setCarsLeft] = useState([]);
  const generateCarLeft = () => {
    for (let i = 0; i < 5; i  ) {
      setTimeout(() => {
        setCarsLeft((items) => [
          ...items,
          <CarLeft key={i} className="car__left">
            <img
              src={carListDay[Math.floor(Math.random() * carListDay.length)]}
              alt=""
            />
          </CarLeft>,
        ]);
      }, 3000);
    }
  };

  generateCarLeft();
  return <div className="traffic__container">{carsLeft}</div>;
}

CodePudding user response:

  1. A React component is essentially just a function, so if you declare a variable inside that function it will be re-created each time you call it. If you want any value to persist between calls it has to be saved somewhere else. In React that 'somewhere' is state.

  2. Unless React detects changes to the state it won't even re-render the component.

So as of now your component is called once, 3 seconds later all 5 new elements are added to the array at once, but the component is no re-rendered, because React is not tracking this change. I'll give you an example of how you make it work, but I'd suggest you learn more about React's concepts of state and life cycle.

function Traffic() {
 [carsLeft, setCarsLeft] = React.useState([]);

 React.useEffect(()=>{
  if(carsLeft.length > 4) return;
   setTimeout(() => {
     setCarsLeft( cl => [...cl,  
      <CarLeft key={i} className="car__left">
        <img
          src={carListDay[Math.floor(Math.random() * carListDay.length)]}
          alt=""/>]
      );
     }, 3000);

   })
  return <div className="traffic__container">{carsLeft}</div>;
}

CodePudding user response:

Even if this did run, it would not run the way you want it to. Because the for loop is extremely fast, you would wait 3s to get 5 cars at once.

You need to use setInterval instead. And you need to wrap all side effects in a useEffect hook, and all persistent variables like carsLeft in a useState hook.

export default function Traffic() {
  const [carsLeft, setCarsLeft] = useState<Array<ReactNode>>([]);
  const interval = useRef<number>();

  useEffect(() => {
    interval.current = setInterval(() => {
      if (carsLeft.length < 5)
        setCarsLeft([
          ...carsLeft,
          <CarLeft key={carsLeft.length} className="car__left">
            <img
              src={carListDay[Math.floor(Math.random() * carListDay.length)]}
              alt=""
            />
          </CarLeft>
        ]);
      else clearInterval(interval.current);
    }, 3000);

    return () => {
      clearInterval(interval.current);
    };
  }, [carsLeft]);

  return <div className="traffic__container">{carsLeft}</div>;
}

Codesandbox demo: https://codesandbox.io/s/naughty-driscoll-6k6u8?file=/src/App.tsx

  • Related