Lazy loading component in react.

Learn how to load component with lazy loading in react.

For example suppose you are visiting an e-commerce website where there are many list of items, It does not makes any sense to load all the items at once, instead it is better to fetch more items after user has seen the previous items.

Often we pre-fetch say 20 items and then fetch more items once the user has seen them by either scrolling down to end or with pagination.

The no of items that needs to fetched depend upon the UI/UX and how many items we want user to see at once.

I have already created the pagination component in react, this time I am going to do the lazy loading.

In this you can determine when do you want to lazy load the items, I will be doing it after user has scrolled to the end vertically.

Lazy loading in react.

There are few extra packages which we will be utilizing for our development.

  • classnames: This helps us to use CSS classes as javascript objects, we just have to name our CSS file as filename.module.css.
  • lodash: Set of helpful utility function.

Following is the folder structure of our component.
React lazy loading component folder structure

Let us begin with our development by importing all required packages and setting up the structure of our component.

import React, { Component } from "react";
import styles from "./index.module.css";
import { debounce } from "lodash";

const URL = "https://reqres.in/api/users";

class LazyLoading extends Component {
  //other code will go here
}

export default LazyLoading;

We are using reqres.in as a dummy api to fetch the data. It helps to fetch the data based on the pagination.

That is on page 1 we will get 6 items on page 2 next 6 items and so on.

Create the state to store the data of the component.

 constructor(props) {
    super(props);
    this.state = {
      list: [],
      currentPage: 0,
      isLoading: false,
      error: false
    };
  }

We are storing some generic items which we need to keep track of in our component. All of them are self explanatory.

We will need a function which will make the API call and fetch the data for us.

fetchData = async () => {
    try {
      const { currentPage, list } = this.state;

      this.setState({
        error: false,
        isLoading: true
      });

      const res = await fetch(`${URL}?page=${currentPage + 1}`);
      const { data } = await res.json();

      this.setState({
        list: [...list, ...data],
        currentPage: currentPage + 1
      });
    } catch (e) {
      this.setState({
        error: true
      });
    } finally {
      this.setState({
        isLoading: false
      });
    }
  };

As we are using async/await, we have wrapped our API call code inside the try/catch block.

Before making the API call we want the component to be in loading state and once we have fetched the data update it in the state along with the current page.

If the API call fails then update the error state of the component and finally irrespective of what happens we want to set the loading state to false.

As we want to pre-fetch some items initally. We will have to call this function inside the life cycle method.

componentDidMount() {
    this.fetchData();
}

This will call the function when the component is mounted or loaded.

Now as we want to fetch the additional items after user have scrolled to the end. We can do this only if we are listening to the scroll event.

Assign the event listener in the same life cycle method.

componentDidMount() {
  this.fetchData();
  window.addEventListener("scroll", debounce(this.lazyLoad, 300));
  window.addEventListener("resize", debounce(this.lazyLoad, 300));
}

We are also listening to the resize event because when the window is resized the dimension of the window changes so we have to make sure that if we have reached to scroll end on resize then also fetch the data.

We are using debounce because we don’t want to keep calling our function as the user keeps scrolling instead we want to make the call once the user has scrolled and stopped.

Creating the lazyLoad which will check if we have reached to the bottom of the scroll or not and then invoke the fetchData.

 lazyLoad = () => {
    const advance = 100;
    const { innerHeight, scrollY } = window;
    const { offsetHeight } = document.body;
    if (innerHeight + scrollY + advance >= offsetHeight) {
      // you're at the bottom of the page
      this.fetchData();
    }
  };

We are using advance to fetch the data before the extreme end of the scroll, but you can keep it as per your requirement

The next thing pending is creating the layout and generating the list of items. We will also show a pre-loader or error message depending upon the state of the component.

  render() {
    const { list, isLoading, error } = this.state;
    
    const items = list.map(e => (
      <div key={e.id} className={styles.item}>
        <div className={styles.wrapper}>
          <img src={e.avatar} alt={e.first_name} />
          <span>
            Name: {e.first_name} {e.last_name}
          </span>
          <span>Email: {e.email}</span>
        </div>
      </div>
    ));

    return (
      <>
        {isLoading && <div className={styles.fullScreenLoader}></div>}
        <div className={styles.container}>{items}</div>
        {error && (
          <div className={styles.error}>
            There was some error while fetching the data
          </div>
        )}
      </>
    );
  }

At the end make sure you remove the event listener when the component is removed.

 componentWillUnmount() {
    window.removeEventListener("scroll", () => {});
    window.removeEventListener("resize", () => {});
  }

Complete code of lazy loading component in react.

import React, { Component } from "react";
import styles from "./index.module.css";
import { debounce } from "lodash";

const URL = "https://reqres.in/api/users";

class LazyLoading extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: [],
      currentPage: 0,
      isLoading: false,
      error: false
    };
  }

  componentDidMount() {
    this.fetchData();
    window.addEventListener("scroll", debounce(this.lazyLoad, 300));
    window.addEventListener("resize", debounce(this.lazyLoad, 300));
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", () => {});
    window.removeEventListener("resize", () => {});
  }

  lazyLoad = () => {
    const advance = 100;
    const { innerHeight, scrollY } = window;
    const { offsetHeight } = document.body;
    if (innerHeight + scrollY + advance >= offsetHeight) {
      // you're at the bottom of the page
      this.fetchData();
    }
  };

  fetchData = async () => {
    try {
      const { currentPage, list } = this.state;
      this.setState({
        error: false,
        isLoading: true
      });

      const res = await fetch(`${URL}?page=${currentPage + 1}`);
      const { data } = await res.json();

      this.setState({
        list: [...list, ...data],
        currentPage: currentPage + 1
      });
    } catch (e) {
      this.setState({
        error: true
      });
    } finally {
      this.setState({
        isLoading: false
      });
    }
  };

  render() {
    const { list, isLoading, error } = this.state;

    const items = list.map(e => (
      <div key={e.id} className={styles.item}>
        <div className={styles.wrapper}>
          <img src={e.avatar} alt={e.first_name} />
          <span>
            Name: {e.first_name} {e.last_name}
          </span>
          <span>Email: {e.email}</span>
        </div>
      </div>
    ));

    return (
      <>
        {isLoading && <div className={styles.fullScreenLoader}></div>}
        <div className={styles.container}>{items}</div>
        {error && (
          <div className={styles.error}>
            There was some error while fetching the data
          </div>
        )}
      </>
    );
  }
}

export default LazyLoading;

Complete style code of lazy loading component

I have kept the style very simple.But feel free to utilize your creativity.

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

.item {
  -webkit-box-flex: 0;
  -ms-flex: 0 0 33.333333%;
  flex: 0 0 50%;
  max-width: 50%;
  padding: 0 10px;
  margin: 10px 0;
}

.wrapper {
  box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.05);
  padding: 25px;
  text-align: center;
  background: #fbe9e7;
}

.wrapper > img {
  display: inline-block;
  width: 100%;
  max-width: 18em;
  margin: 0 auto;
}

.wrapper > span {
  display: inline-block;
  width: 100%;
  margin: 5px;
}

.fullScreenLoader {
  position: fixed;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 99999;
  background-color: rgba(0, 0, 0, 0.75);
}

.error {
  color: red;
  text-transform: capitalize;
  font-size: 18px;
}

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import LazyLoading from "./Components/Lazyloading";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <LazyLoading />
  </div>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Output

React lazy load