• Go
  • React

Learn React hooks by Creating a Typing component

In this tutorial we'll be building a component to create typing effect using useState and useEffect hooks. Before we go ahead Here's the link to Codesandbox if you want to have a look

Let's understand the basic of these hooks before we start working on our awesome typing app :p

  • useState

    This hook helps us in saving our local state (state of the component), its definition is as follows
// initialState is the intial state of our component,
// setState is a function which we can use to update the state
import { useState } from "react";

const [state, setState] = useState(initialState);

There's no limitations on the number of times this hook can be called,

//we can separate our state like this
const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Wick");

// we can use a single hook to store all of our state as well( Component level)

const [fullName, setFullName] = useState({
  firstName: "John",
  lastName: "Wick"
});
  • useEffect

    This hook helps us in handling side effects for our component Which was earlier handled using the life cycle methods in class based components, its definition is as follows
import { useEffect } from "react";

useEffect(() => {
  // handle side effects
  sideEffect();

  return () => {
    // handle cleanup here,
    // this function will be called on unmount of component
  };
}, [dependenciesArray]);

This hook can be used in the following ways to replace all the previous life cycle methods

// if no dependencies are passed, this effect is called on every render
useEffect(() => {
  // perform the effect on every render
});

// called when the component mounts,
// since there are no dependencies this is called only once,
// Matching Life cycle : componentDidMount

useEffect(() => {
  // on mount
}, []);

// the function returned by the effect is called when the component is about to unmount
// Matching Life cycle : componentWillMount
useEffect(() => {
  return () => {
    //on unmount
  };
}, []);

// if you pass any dependencies the effect will be triggered only when there's a change in any of value in the dependency array
//here the effect is triggered when either of the firstName or lastName change
useEffect(() => {
  // effect
}, [firstName, lastName]);

// like useState, useEffect can also be called any number of times
useEffect(() => {
  // effect
}, [firstName]);
useEffect(() => {
  // effect
}, [lastName]);

This description was provided to help understand the example we'll be building and is by no means a complete guide on these hooks, please go through the official docs for deeper understanding and to know other use cases.

Lets start with our Typing component Here we'll pass the text to be typed as a prop along with delay, which is the number of milliseconds to wait before typing the next letter.

we'll store the current text which is being typed in the text variable using our useState hook as follows

import React, { useState } from "react";

function TypingComponent({ textToType, delay }) {
  const [text, setText] = useState("");
  return <div>{text}</div>;
}

Now as you might've already guessed we'll be using the setTimeout to handle the delays in the typing these letters, and update our text using the setText method after the delay. But where do we call it? Since its a side effect, we need to use the useEffect hook

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

function TypingComponent({ textToType, delay }) {
  const [text, setText] = useState("");
  useEffect(() => {
    setTimeout(() => {
      //   update our text here
    }, delay);
  });
  return <div>{text}</div>;
}

but how do we keep track of the current index ? Yes we need another variable

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

function TypingComponent({ textToType, delay }) {
  const [text, setText] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  useEffect(() => {
    setTimeout(() => {
      setText(text + textToType[currentIndex]);
      setCurrentIndex(currentIndex + 1);
    }, delay);
  });
  return <div>{text}</div>;
}

Now things are getting better, but is it? We're calling the setTimeout on each render which might effect the performance of our app So lets add the dependency, what do we add ? Since we're updating only when the index changes, we can add it as as dependency we beed to handle the case where we get past the textToType length, so lets handle it as well

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

function TypingComponent({ textToType, delay }) {
  const [text, setText] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  useEffect(() => {
    if (currentIndex < textToType.length) {
      setTimeout(() => {
        setText(text + textToType[currentIndex]);
        setCurrentIndex(currentIndex + 1);
      }, delay);
    }
  }, [currentIndex]);
  return <div>{text}</div>;
}

Now what if we need it to reset and start again ? lets add a prop and handle that case as well

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

function TypingComponent({ textToType, delay, loop }) {
  const [text, setText] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  useEffect(() => {
    if (currentIndex < textToType.length) {
      setTimeout(() => {
        setText(text + textToType[currentIndex]);
        setCurrentIndex(currentIndex + 1);
      }, delay);
    } else if (loop) {
      // reset the text and the index
      setText("");
      setCurrentIndex(0);
    }
  }, [currentIndex]);
  return <div>{text}</div>;
}
function App() {
  return (
    <div className="App">
      <TypingComponent textToType="Thank you..." delay={300} loop={true} />
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Here's the final output