DEV Community

Omi
Omi

Posted on

Implementing a basic loading state | 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.

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, it is wrong. 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.

Catch-1: Including dots in the dependency array of useEffect does not make sense. Because then it calls the window.setInterval on each update of the dots state. (Can use window.setTimeout instead. But why?)

Catch-2: The Loading component's dots state should be dependent on the useEffect and window.setInterval. However, using dots directly in the useEffect makes the useEffect depend upon it.

Before going to the next step, think about Closures.

Revised approach

Define useEffect's callback's own dots state (say effectDots). The window.setInterval's callback function increments the effectDots and also sets the Loading Component's dots state.
(The key is to alter the dependency from setInterval -> component's dots state to component's dots state -> setInterval. )

Revised version of callback functions of useEffect and window.setInterval with a local state effectDots:

  useEffect(() => {
    let effectDots = 1;
    window.setInterval(() => {
      // increment, modulo 4
      // set the Loading component's state
      setDots(++effectDots % 4);
    }, 1000);
  }, []);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)