Create a text typing effect with React

The typing animation effect is a very popular effect on the web. It can be used to create a chat, a loading animation, or just to add a bit of dynamism to your website.

Since OpenAI has released ChatGPT (already almost a year!), we can see this effect more than ever. For a project, I had to create a similar effect, and I thought it would be a good idea to share it with you.

The effect

The code

For this effect, we will use a custom hook:

const useTypingEffect = (
  text: string,
  duration: number,
  isTypeByLetter = false
) => {
  const [currentPosition, setCurrentPosition] = useState(0);
  const items = isTypeByLetter ? text.split("") : text.split(" ");

  useEffect(() => {
    setCurrentPosition(0);
  }, [text]);

  useEffect(() => {
    if (currentPosition >= items.length) return;

    const intervalId = setInterval(() => {
      setCurrentPosition((prevPosition) => prevPosition + 1);
    }, duration);

    return () => {
      clearInterval(intervalId);
    };
  }, [currentPosition, items, duration]);

  return items.slice(0, currentPosition).join(isTypeByLetter ? "" : " ");
};

We have 3 parameters: the text to display, the duration between each letter/word, and a boolean to know if we want to type by letter or by word (I use this in the next section).

In our example below we use the hook like with a texts array, and we change the text every 5 seconds, like this:

import useTypingEffect from "./useTypingEffect";

const texts = [
  "This is a simple text typing effect in React",
  "This effect is created using React Hooks",
  "We can use this effect to create a typing effect for our portfolio",
  "We can also use this effect to create a typing effect for our resume",
  "or for your blog",
  "or for your landing page",
  "let's go",
];

type TextTypingEffectProps = {
  isTypeByLetter?: boolean;
  duration?: number;
};

export const TextTypingEffectWithTexts: React.FC<TextTypingEffectProps> = ({
  isTypeByLetter = false,
  duration = 200,
}) => {
  const [textIndex, setTextIndex] = React.useState(0);
  const textToShow = useTypingEffect(
    texts[textIndex],
    duration,
    isTypeByLetter
  );

  React.useEffect(() => {
    const intervalId = setInterval(() => {
      setTextIndex((prevIndex) =>
        prevIndex >= texts.length - 1 ? 0 : prevIndex + 1
      );
    }, 5000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return (
    <span className="text-black dark:text-white" key={textIndex}>
      {textToShow}
    </span>
  );
};

More

This is very simple hook, we can improve it a bit in term of animation. For example, we can take inspiration from chat.openai, and add a fade out effect and a cursor.

// ...

const TIME_TO_FADE = 300;
const TIME_INTERVAL = 3000;
const TIME_PER_LETTER = 100;

export const TextTypingEffectWithTextsFadeOut = () => {
  const [textIndex, setTextIndex] = React.useState(0);
  const [fadeText, setFadeText] = React.useState(true);
  const [fadeCircle, setFadeCircle] = React.useState(true);
  const textToShow = useTypingEffect(texts[textIndex], TIME_PER_LETTER, false);

  const timeToTypeText = texts[textIndex].split(" ").length * TIME_PER_LETTER;

  React.useEffect(() => {
    const circleTimeout = setTimeout(() => {
      setFadeCircle(false);
    }, timeToTypeText + 1000);

    const textTimeout = setTimeout(() => {
      setFadeText(false);

      setTimeout(() => {
        setTextIndex((prevIndex) =>
          prevIndex >= texts.length - 1 ? 0 : prevIndex + 1
        );
        setFadeCircle(true);
        setFadeText(true);
      }, TIME_TO_FADE);
    }, TIME_INTERVAL);

    return () => {
      clearTimeout(circleTimeout);
      clearTimeout(textTimeout);
    };
  }, [textIndex]);

  return (
    <>
      <span
        className={`inline-flex items-center text-black duration-300 dark:text-white ${
          fadeText ? "opacity-1 translate-y-0" : "translate-y-2 opacity-0"
        }`}
        key={textIndex}
      >
        {textToShow}{" "}
        <div
          className={`ml-2 h-3 w-3 rounded-full bg-black duration-300 dark:bg-white ${
            fadeCircle ? "" : "h-0 w-0 opacity-0"
          }`}
        />
      </span>
    </>
  );
};
Published: 9/12/2023

Subscribe for project updates, links, and personal notes.