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;
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);
And add the dots after loading text
{".".repeat(dots)}
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);
3) Create a useEffect
hook that only runs once after initial render.
useEffect(() => {
window.setInterval(() => {
// Logic to increment dots
}, 1000);
}, []);
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);
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);
}, []);
Top comments (0)