I’m making a game about pokemon in React and there’s a component called Announcer which announces what is happening in it.
I made the letters appear one by one just like old games and I wanted that this behavior would happen one message at a time, but React is rendering all messages simultaneously:
https://i.sstatic.net/IYLLcYTW.gif
Here’s the code of the Announcer and AnnouncerMessage
import AnnouncerMessage from "./AnnouncerMessage";
const Announcer = ({ messages }) => {
return (
<div className="announcer">
<div className="announcerMessages">
{messages.map((msg) => (
<AnnouncerMessage message={msg} />
))}
</div>
</div>
);
};
export default Announcer;
import { useState, useEffect, useRef } from "react";
const AnnouncerMessage = ({ message }) => {
const [placeholder, setPlaceholder] = useState("");
const index = useRef(1);
useEffect(() => {
const tick = () => {
setPlaceholder(message.slice(0, index.current));
index.current++;
};
if (index.current <= message.length) {
setTimeout(tick, 25);
}
}, [placeholder]);
return <p>{placeholder}</p>;
};
export default AnnouncerMessage;
2
Answers
What happens in your code essentially is each
AnnouncerMessage
component starts its “letter-by-lteer animation” as soon as it’s rendered.This happens because each timeout you define in each
AnnouncerMessage
component is independent from the others.One way of solving this, is simply to have a “global” counter (in the StackBlitz example below
currentMessageIndex
) which keeps tract of which component is rendering.Then it’s just a matter of defining a callback function (
handleMessageComplete
in the example) which increments the global counter.Additionally, you need to have a dedicate props (
isCurrent
in the example) to keep track whether the current component is being render or was rendered before.We need this to disable the animation on the components already rendered, since React will re-render each child component once a state update happens on the parent component.
Hope this helps, below you can find the StackBlitz repo with the full example.
Announcer
AnnouncerMessage
StackBlitz Repo
The issue with your implementation is that all the
AnnouncerMessage
components are rendering simultaneously because each one starts its animation immediately upon being mounted. To resolve this you need to control when each message starts rendering. A common approach is to use a state to keep track of the current message being displayed and only render one message at a time.