This is a simple for loop that runs 80 times but only every 100 ms. Each time it runs, it pushes a Y coordinate for a character into the stillChars
state array. The stillChars
state array is mapped through to create a sequence of text elements directly underneath eachother.
See this video to see the animation
const StillChars = () => { const [stillChars, setStillChars] = useState([]); function addChar() { for (let i = 0; i < 80; i++) { setTimeout(() => { setStillChars((pS) => [ ...pS, { y: 4 - 0.1 * 1 * i, death: setTimeout(() => { setStillChars((pS) => pS.filter((c, idx) => idx !== i)); }, 2000), }, ]); }, i * 100); } } useEffect(() => { addChar(); }, []); return ( <> {stillChars.map((c, i) => { return <StillChar key={i} position={[0, c.y, 0]} i={i} />; })} </> ); };
Desired behavior: The death: setTimeout
function should remove the characters like a queue instead of whatever is going on in the video/codesandbox.
Advertisement
Answer
Your main mistake is with:
setStillChars((pS) => pS.filter((c, idx) => idx !== i))
The problem is that this sits within a setTimeout()
so the above line runs at different times for each iteration that your for loop does. When it runs the first time, you update your state to remove an item, which ends up causing the elements in your array that were positioned after the removed item to shift back an index. Eventually, you’ll be trying to remove values for i
that no longer exist in your state array because they’ve all shifted back to lower indexes. One fix is to instead associate the index with the object itself by creating an id
. This way, you’re no longer relying on the position to work out which object to remove, but instead, are using the object’s id
, which won’t change when you filter out items from your state:
{ id: i, // add an `id` y: 4 - 0.1 * 1 * i, death: setTimeout(() => { setStillChars((pS) => pS.filter(c => c.id !== i)); // remove the item based on the `id` }, 2000), },
Along with this change, you should now change the key
within your .map()
to use the object’s id
and not its index, otherwise, the character won’t update in your UI if you’re using the same index to represent different objects:
return <StillChar key={c.id} position={[0, c.y, 0]} i={i} />;
As also highlighted by @KcH in the question comments, you should also remove your timeouts when your component unmounts. You can do this by returning a cleanup function from your useEffect()
that calls clearTimeout()
for each timeout that you create. In order to reference each timeout, you would need to store this in an array somewhere, which you can do by creating a ref with useRef()
. You may also consider looking into using setInterval()
instead of a for loop.