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:
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.
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