The simple concept behind a react loader (working progress bar)

February 05, 2019

A post by Will Mayger
Twitter . Instagram

Ever wondered how to make a real react loader that actually works instead of creating a fake one that just looks like it works?

Yes? No? Either way, you will learn the concepts of making a proper react loader / progress bar here.

You usually need some kind of loader when the content within the site is so massive (usually because of images) and you can’t do any more compression.

The first one I ever created was a fake react loader, mostly because of a tight deadline but partially because it seemed easier to do so, which it was, but not by much. It would have been worth the time to do it properly as it really doesn’t take very long at all and It would have worked properly.

Anyway, you learn from your mistakes.

After you have learnt the concepts of it, then you will be able to apply it anywhere, you can even use this outside of react.js because react still uses vanilla js just like everything else, which is what the main concept will be based on!

Concept one - Event listeners.

Make use of event listeners. They will tell you when the element has loaded, and that is what you can base your loader on, making it the main concept.

You can do this multiple ways with react.

But there will most likely be a trend of using element.addEventListener('load', callback);.

Vanilla.js

You can make use of the vanilla.js ability to see and interact with a parent element’s child elements. This means you can apply on load event listeners.

This is my preferred method because it is very easy and time efficient to implement. You can do this with refs or with id’s and classes.

Here is a small snippet of code showing how you can do this (I will use id’s to make it easy to understand):

JSX

<div id=”parent”>
    <img src=”child.jpg” alt=”child” />
</div>

Vanilla JS & React

// elements
const parent =  document.getElementById('parent');
const children = parent.getElementsByTagName('*');

// arrow function for ease of using “this” variable
const onLoadEvent = () => {
    // use the setState callback option to always get the latest state object
    this.setState(state => ({ currentlyLoaded: state.currentlyLoaded + 1  }));
};

// for loop as the children variable is a node array
// you could also use a recursive function
for (let i = 0; i <= children.length; i++) {
    // assign the on load event listener to the elements on load event
    children[i].addEventListener('load', onLoadEvent);
}
Props

React props are attributes that you pass into a react component.

When you pass a component into another component within JSX it becomes a child.

When you have a react component that you add children into, you can actually modify the props passed into the child elements, this means you could attach refs, or even attach on load event listeners directly onto the react child components. You can then store the responses in the parent component without ever affecting the child element.

Hardcoding

You can directly add on load event listeners to the elements / react components. This may be a good approach if you will always have the same amount of images and if there aren’t too many images costing you time.

You can do this any way you want to in react, I am using vanilla to make it easy to understand for everyone and it is a solid way do doing it and would highly recommend it.

Concept two - Numbers.

You will need to know the total amount of elements that are waiting to be loaded to be able to create an accurate react loader.

Once you have this number, you will need to save it to your parent react components state.

On each of the child elements on load event listener you will want to assign a function that adds 1 to a counter that is saved in the state when that element loads.

When you have the same number in the counter as the amount of child elements you have then you will know that everything has loaded. This is how you can tell the parent element to display the react loader or display the child elements. Simply display the react loader component until the numbers equal each other. After they match, you can then display the child elements as this means they would have loaded.

This might look a little bit like the code snippet below:

Vanilla JS & React

// elements
const parent =  document.getElementById('parent');
const children = parent.getElementsByTagName('*');

if (this.state.totalElements === -1) {
    this.setState({ totalElements: children.length })
}

// arrow function for ease of using “this” variable
const onLoadEvent = () => {
    // use the setState callback option to always get the latest state object
    this.setState(state => ({ currentlyLoaded: state.currentlyLoaded + 1  }));
};

// for loop as the children variable is a node array
// you could also use a recursive function
for (let i = 0; i <= children.length; i++) {
    // assign the on load event listener to the elements on load event
    children[i].addEventListener('load', onLoadEvent);
}

if (this.state.totalElements === this.state.currentlyLoaded) {
    // then elements have all loaded and return without the loader
    return (...);
}

// return elements with react loader jsx on top
return (...);

Concept three - Calculating percentages

The next thing to do is calculate the percentages to be able to add smooth animations to our loading screen. This is essential, especially if you want a react loader with a progress bar!

This part is actually very easy we just need to workout the percentage of the current loaded elements from the total amount of elements.

To do this we just need to use a simple bit of math like the following:

const total = 10; // total elements
const current = 5; // current loaded elements
const percentage = Math.floor(current / total * 100); // outcome of this would be 50%

You need to divide the amount of elements that have loaded by the total number of elements and then move the decimal two places. Just finish up by rounding the number down and you will have your percentage that you can use for your react loader progress bar!

Then I would recommend that you apply this to your ui by using the maxWidth style attribute within JSX and storing the rest of the css for the progress bar in another file like a sass or css modules file.

(I use css modules and explain why here)

JSX

<div className={styles.progressBarContainer}>
    <span className={styles.progress} style={{ maxWidth: `${calculatedPercentage}%` }}>
</div>

Css module styles

.progressBarContainer {
    max-width: 320px;
    width: 100%;
    height: 10px;
    border: 1px solid #000;
    margin: 0 auto;
    background-color: transparent;
}

.progress {
    display: block;
    max-width: 0;
    width: 100%;
    height: 100%;
    background-color: green;
    transition: 0.1s all;
}

A good tip is to add a very small transition to animations if you want them to look nice and smooth, like I have done here for our react loader using transition: 0.1s all;!

Now every time our on load functions update the state, this in turn will update our percentage making our progress bar start working as it should!

Concept four - React loader screen

This concept is the easiest part, and you simply need to create a loading screen for your react loader!

This loader screen sits over the top of the elements that you want to load in so you can hide them from the user and display a nice, user friendly display rather than a broken looking page. You can do this with a little css, so nothing fancy here!

Your react loader screen must be light weight otherwise you will need a loader for your react loader, which, for obvious reasons, you don’t want.

If you can avoid using images then perfect! This will mean you can create a loader that is very fast and will render in immediately.

If you do have to use images, make sure they are tiny. I’m talking about 10-30kb kind of tiny.

Now all you need to do is display the loading screen until the total number of elements match the loaded number of elements.

Concept five - Tidying up

To finish our react loader off (and this blog post) we now need it to remove all the event listeners to provide the best performance.

This is really easy, we just need to call element.removeEventListener('load', callback), in the same way that we called it in the first place. We just need to add in a flag to ensure that we have not already removed the event listener and then remove it, leaving us with a clean element that has been loaded in safely so the user remains happy!

(Using the earlier example)

if (!this.removed) {
    for (let i = 0; i <= children.length; i++) {
        // assign the on load event listener to the elements on load event
        children[i].removeEventListener('load', onLoadEvent);
    }
    this.removed = true;
}

Thanks for reading,

Will