The goal of every application is to keep its users satisfied and eager to see more. Therefore, it is important to make the right decision regarding displaying data, considering both application performance and user experience. Some ways to display data would include pagination and infinite scrolling, both widely popular.

Pagination vs. Infinite Scroll

Pagination is often the first data representation method many developers face, and it can also be considered the easiest. It is based on separating content into different pages, where each page has a constant page size. However, user interaction with multiple steps is required in order to change the page size or page number, which can lead to a breaking of user flow and a less satisfying user experience.

Infinite Scroll offers a better user experience by removing additional steps in order to load data, which is now loaded automatically once the user scrolls down. Once a user approaches the end of a loaded list by scrolling, instead of changing content like with pagination, data from the next page will be added to the end of previously loaded data. This could cause a problem for a large set of data since the list just keeps growing, filling memory and slowing down applications.

As we can see, where pagination fails, infinite Scroll exceeds, and vice versa. That is where virtual Scroll comes in, representing infinite Scroll with optimized performance thanks to some of the pagination features.

Why use Virtual Scroll?

Virtual Scroll inherits the user experience of infinite Scroll, where the user has to scroll in order to change displayed data. However, unlike with infinite Scroll, the list does not expand. Instead, we always have a constant number of elements in memory, like with pagination. They change depending if they are currently in the user viewport, making it a better choice than Infinite Scroll when dealing with a large data set. You might be wondering how we can achieve a smooth scrolling experience while changing the data. In addition to the viewport, we always render extra elements above and below. As the user scrolls up and down, we recalculate which elements should be removed and which should be added to the list. This way, users don’t need to wait for data to be loaded because it happens instantly when a new element comes into the viewport, giving them the impression that the complete list of data is present throughout the entire scrolling process.

Implementing Virtual Scroll in React

Even though there is a certain number of libraries that deal with virtual scrolling, it is quite helpful to understand the logic behind it. While there are many different ways to implement virtual scroll, a common aspect among them is setting a fixed height for the main container and applying the CSS property ‘overflow: scroll.’, as shown below in VirtualScroll.css:

.scroll-container {
    height: 800px;
    overflow: scroll;

    .row-container {
        height: 100px;

This allows scrolling within the container, creating an infinite scroll effect. Having fixed height for rows inside an Infinite Scroll is also important as it helps us make sure that the number of items we load really is the number of items that can fit into the viewport.

Reference to that container would help us determine the current scroll position and trigger the scroll event. With the help of scroll position, containerHeight and rowHeight, we can with every scroll recalculate the starting and ending index of the visible date and fetch the date with that information. Below you can find an example code for the component Virtual Scroll that behaves as described above. For the purpose of demonstrating fetching data, a mock api call returning a list of strings was created to represent data change.

import React from 'react';

import './VirtualScroll.css'

class VirtualScroll extends React.Component {
  constructor(props) {

    this.state = {
      visibleItems: [],
      scrollTop: 0

    this.scrollContainerRef = React.createRef();

  componentDidMount() {

  callApi(startIndex, endIndex) {
    return new Promise((resolve) => {
        const items = [ ];
        for (let index = startIndex; index < endIndex; index++) {
            items.push('item l ' + index);


  updateVisibleItems() {
    const scrollContainer = this.containerRef.current;
    const containerHeight = scrollContainer.clientHeight;
    const rowContainer = scrollContainer.querySelector(.row-container);
    const rowHeight = rowContainer ? rowContainer.clientHeight : 0;
    const totalItems = this.state.visibleItems.length;
    const visibleItems = Math.ceil(containerHeight / rowHeight);
    const scrollOffset = scrollContainer.scrollTop;
    const start = Math.max(0, scrollOffset - visibleItems);
    const end = Math.min(totalItems, start + visibleItems + 1); 
    this.apiCallback(start, end)
        .then(data => this.setState({ visibleItems: data }));

  render() {
    const { visibleItems } = this.state;
    return (
      <div ref={ this.containerRef } className="scroll-container" onScroll={() => updateVisibleItems() }>
          {, index) => (
            <div key={ index } className="row-container">
              { item }

export default VirtualScroll;

It is important to note that this code represents only the basic implementation of the Virtual Scroll in order to illustrate the concept correctly. Also, described steps are only part of one of the ways to implement this data displaying technique, while some of the other ways would include remembering starting and ending indexes, using Interaction Observers, dividing data into three sections, and many more.


Before jumping into using Virtual Scroll, it is important to note that implementation of it, whether you are using a library or custom solution, is more complex and harder than implementing pagination and infinite scroll, and it might not be the best solution if you are tight on schedule. However, in case you do have the necessary time, Virtual Scroll in React could be an ideal way to keep your user happy, both performance and experience-wise.

“The Magic of Virtual Scroll in React: Optimizing Performance and User Experience” Tech Bite was brought to you by Nađa Alijagić, Junior Software Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

Software DevelopmentTech Bites
February 23, 2024

Background Jobs in Elixir – Oban

When and why do we need background jobs? Nowadays, background job processing is indispensable in the world of web development. The need for background jobs stems from the fact that synchronous execution of time-consuming and resource-intensive tasks would heavily impact an application's  performance and user experience.  Even though Elixir is…

Want to discuss this in relation to your project? Get in touch:

Leave a Reply