DEV Community

Omi
Omi

Posted on • Edited on

Implementing a basic loading state | useState | Closure

How do you create a basic loading state using html, javascript and reactjs hooks?

Requirements:
1) React functional component.
2) It should just return loading text: "Loading".
3) Show the dots being added incrementally (+1) to the end of the loading text every second.
For example:
Loading. -1s- Loading.. -1s- Loading... -1s- Loading

Approach:

Decide the static elements. Then add the dynamics (states, hooks etc). As per the thinking in React doc.

Static element implementation:

1) Create a functional component that returns "Loading".

const Loading = () => {
  const loadingText = "Loading";

  return (
    <div>
      <h2>{loadingText}</h2>
    </div>
  );
};

export default Loading;
Enter fullscreen mode Exit fullscreen mode

Dynamics:

1) The number of dots represents a state of the component. So, define a state variable using useState. Initial value of the state = 1.

const [dots, setDots] = useState(1);
Enter fullscreen mode Exit fullscreen mode

And add the dots after loading text

{".".repeat(dots)}
Enter fullscreen mode Exit fullscreen mode

2) A state changes automatically after each second. window.setInterval can perform this task. Leave the callback function empty for now.

window.setInterval(() => {
      // Logic to increment dots
    }, 1000);
Enter fullscreen mode Exit fullscreen mode

3) Create a useEffect hook that only runs once after initial render. Notice the empty array as a second argument. Check this Reactjs doc to how second arguments behave differently.

useEffect(() => {
    window.setInterval(() => {
      // Logic to increment dots
    }, 1000);
  }, []);
Enter fullscreen mode Exit fullscreen mode

Till now, the app only shows "Loading.".
Take a pause and think of the logic inside window.setInterval callback function.

The obvious looking solution:

setDots((dots + 1) % 4);
Enter fullscreen mode Exit fullscreen mode

However, the component will only go from
"Loading."-1s-"Loading..". Then it will get stuck.

Reason: The useEffect's callback fn is triggered on the initial state of the dots (1) only. Any further state changes in dots state does not affect the closure of useEffect's callback fn.

To achieve proper functionality, update it using previous state as per mentioned in the recatjs docs.

setDots((dots) => (dots + 1) % 4);
Enter fullscreen mode Exit fullscreen mode

The Loading component mounts and unmounts at times. It is a good practice to cleanup every entities that are created during the useEffect's callback and that is not going to get used anymore. In this case, it is the window.setInterval definition.

The cleanup function runs not only during unmount, but before every re-render with changed dependencies.

The cleanup logic for useEffect hook can be defined using a callback function that gets returned by the callback function of useEffect. Check the example below:

 useEffect(() => {
    const interval = window.setInterval(() => {
      setDots((dots) => (dots + 1) % 4);
    }, 1000);

    return () => {
      window.clearInterval(interval);
    };
  }, []);
Enter fullscreen mode Exit fullscreen mode

Finally, the implementation of Loading component looks like below:

import { useEffect, useState } from "react";

const Loading = () => {
  const [dots, setDots] = useState(0); // defined dots state
  const loadingText = "Loading";

  // useEffect hook that gets called on initial render of the Loading component
  useEffect(() => {
    // Defining interval
    const interval = window.setInterval(() => {
      // Setting state using a previous state
      setDots((dots) => (dots + 1) % 4);
    }, 1000);

    // Cleanup logic: 
    return () => {
      window.clearInterval(interval);
    };
  }, []);

  return (
    <div>
      <h2>
        {loadingText}
        {".".repeat(dots)}
      </h2>
    </div>
  );
};

export default Loading;
Enter fullscreen mode Exit fullscreen mode

Top comments (0)