React: Typewriter Effect
I have a short and sweet little React app to write about this month, which achieves a Typewriter effect. When given a word, this word gets spelled out one letter at a time, with a 500ms delay between each letter.
Let's start with a basic React component:
// components/Typewriter.jsexport default function Typewriter({ children: character }) { return character;}
We'll use this in our App like so:
// App.jsimport { useState } from "react";import Typewriter from "./components/Typewriter";
export default function App() { const [word, setWord] = useState("Howdy");
return ( <div className="App"> <p> {word.split("").map((character, index) => ( <Typewriter key={index}>{character}</Typewriter> ))} </p> </div> );}
So we've split the word up into distinct characters and passed them off to the Typewriter
component. We can now update Typewriter.js
to start showing those characters. The first thing we'll do is use a showCharacter
state variable, initially set to false
, that corresponds to whether or not that character is shown.
// components/Typewriter.jsimport { useState } from "react";
export default function Typewriter({ children: character }) { const [showCharacter, setShowCharacter] = useState(false);
return showCharacter && character;}
Now we can use a setTimeout
function within useEffect
to show that character after 500ms:
// components/Typewriter.jsimport { useState, useEffect } from "react";
export default function Typewriter({ children: character }) { const [showCharacter, setShowCharacter] = useState(false);
useEffect(() => { const timer = setTimeout(() => { setShowCharacter(true); }, 500);
return () => { clearTimeout(timer); }; }, []);
return showCharacter && character;}
So the component lifecycle right now is this: The component mounts and showCharacter
is false
. After 500ms, showCharacter
is set to true
, so the character is returned by the component, making it show up in the DOM. When the application is closed and the component unmounts, useEffect
runs its cleanup function, removing the setTimeout
function.
The problem here is that after 500ms, every letter in the word is shown all at once. What we want is for the first letter to appear after 500ms, the second letter to appear after 1000ms, the third letter to appear after 1500ms, and so on. We can adjust the interval amount by using the index
of each letter in the word to achieve this. The formula for doing so is:
(index * 500) + 500
So if index
is 0
, then we just get 500
, if index
is 1
, we get 1000
, if index
is 2
, we get 1500
. This way the letters have a staggered delay. I'll add a stagger
prop and adjust the setTimeout
function to use this formula.
// components/Typewriter.jsimport { useState, useEffect } from "react";
export default function Typewriter({ stagger, children: character }) { const [showCharacter, setShowCharacter] = useState(false);
useEffect(() => { const timer = setTimeout(() => { setShowCharacter(true); }, stagger * 500 + 500);
return () => { clearTimeout(timer); }; }, [stagger]);
return showCharacter && character;}
Now the last thing that I need to do is pass that stagger
prop with the index
value. Back in App.js
:
// App.jsimport { useState } from "react";import Typewriter from "./components/Typewriter";
export default function App() { const [word, setWord] = useState("Howdy");
return ( <div className="App"> <p> {word.split("").map((character, index) => ( <Typewriter key={index} stagger={index}> {character} </Typewriter> ))} </p> </div> );}
That's it! Short and sweet, very simple.