DEV Community

George K.
George K.

Posted on

Creating an animated typing effect in React

A layout component to create a typewriter typing animation effect for text

The idea behind this is that we render text from the state and keep updating state variable one character at a time with a bit of delay.

Each time state is updated our component will re-render showing text "typed" one character after another.

So first thing in our React app we will create a component. Let's name it TextTyper. We will need to have useState and useEffect hooks in it, so we can import them right away:

import React, { useState, useEffect } from "react";

const TextTyper=() => {
  const [typedText, setTypedText] = useState("");

  return <span>{typedText}</span>
}

export default TextTyper
Enter fullscreen mode Exit fullscreen mode

In state we have declared a variable typedText which is rendered inside span HTML element. This would be the variable which we are going to update.

Let's say we want to print "Banana". First we will render empty string, then we will add "B" to typedText and see it in the page, then we will update it to "Ba", and so on...

Let's create a function typingRender. It will take 3 arguments: the whole phrase to type, the updater method which will be our setTypedText and an interval value to tell the function the delay between rendering each character.

In this function we will use variable localTypingIndex to go through the indexes of characters in the incoming string and take them one by one. We will also declare a variable localTyping to mirror what should be in state and add letters to it to put to state:

import React, { useState, useEffect } from "react";

const TextTyper=() => {
  const [typedText, setTypedText] = useState("");

  const typingRender = (text, updater, interval) => {
// local counter used to go through indexes of the phrase
    let localTypingIndex = 0;
// local string to add characters to and put to state
    let localTyping = "";
    if (text) {
// if we have a phrase to type we will start the interval
      let printer = setInterval(() => {
// if our local index counter is less than the length of the phrase we keep going
        if (localTypingIndex < text.length) {
// we set to state our local string with the phrase
          updater((localTyping += text[localTypingIndex]));
// and increase the local index
          localTypingIndex += 1;
        } else {
// once we reached the end of the phrase we reset local index
          localTypingIndex = 0;
// clear local string with phrase
          localTyping = "";
// clear the interval to stop
          clearInterval(printer);
        }
      }, interval);
    }
  };

  return <span>{typedText}</span>
}

export default TextTyper
Enter fullscreen mode Exit fullscreen mode

Now we have our typing function it's time to execute it. We want this to happen once when component is initially rendered so we will use useEffect with empty array of dependencies:

const TextTyper=() => {
  const [typedText, setTypedText] = useState("");
// declare the variable to hold the phrase
const text = 'This will be the phrase printed one by one character'
// declare a variable with the interval of 100 milliseconds
const interval = 100
  const typingRender = (text, updater, interval) => {
    let localTypingIndex = 0;
    let localTyping = "";
    if (text) {
      let printer = setInterval(() => {
        if (localTypingIndex < text.length) {
          updater((localTyping += text[localTypingIndex]));
          localTypingIndex += 1;
        } else {
          localTypingIndex = 0;
          localTyping = "";
          clearInterval(printer);
        }
      }, interval);
    }
  };
// run this effect on first render
  useEffect(() => {
// call the function passing a phrase, setter method for state and interval var
    typingRender(text, setTypedText, interval);
  }, [text, interval]);

  return <span>{typedText}</span>
}

export default TextTyper
Enter fullscreen mode Exit fullscreen mode

Now the component will work perfectly fine but we can make it more flexible, so all the data for it to work will come via props and we will even set the variable to control inside which HTML element we want to render our phrase:

import React, { useState, useEffect } from "react";

const TextTyper=({
// now the phrase, interval and HTML element desired will come via props and we have some default values here
  text = "",
  interval = 100,
  Markup = "span"
}) => {
  const [typedText, setTypedText] = useState("");

  const typingRender = (text, updater, interval) => {
    let localTypingIndex = 0;
    let localTyping = "";
    if (text) {
      let printer = setInterval(() => {
        if (localTypingIndex < text.length) {
          updater((localTyping += text[localTypingIndex]));
          localTypingIndex += 1;
        } else {
          localTypingIndex = 0;
          localTyping = "";
          clearInterval(printer);
        }
      }, interval);
    }
  };
  useEffect(() => {
    typingRender(text, setTypedText, interval);
  }, [text, interval]);

  return <Markup>{typedText}</Markup>
}

export default TextTyper
Enter fullscreen mode Exit fullscreen mode

So now if we want to use this we can import the component and render it with passing some props:

// importing our component in App.js
import TextTyper from './TextTyper'

function App() {
const textToRender = 'This will be the phrase printed one by one character'
  return (
    <div className="App">
 {/* rendering it passing */}
    <TextTyper text={textToRender} interval={10} Markup={"code"} />
    </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here is a link to the working code sandbox.

You can also install and import this component with npm or yarn

Top comments (0)