7 Comments

Scrollable Grid with Just-in-Time Data Loading – Part 1: Using React Window

Say you have thousands (or even just hundreds) of rows of data to render. To provide a good experience for the user, you might not want to fetch all the data at once. Instead, you might want to fetch the first hundred, show those, then fetch the next hundred.

Alternatively, you could fetch all the data at once but not render all the rows—just the elements that the user would see. This is, essentially, “windowing” or “virtualizing” your data.

React Window’s InfiniteLoader provides a loader to fetch data in batches, and react-window supplies UI components (such as list and grid) that render a windowed chunk of rows.

Although rendering complex grids still requires adding some functionality, the react window library makes getting started with batch fetching and rendering rows pretty straight forward.

1. InfiniteLoader: Fetching the Data

InfiniteLoader lets you fetch row data in chunks that can then be just-in-time rendered when the rows scroll into view.

It requires these props:

Property Description
isItemLoaded A function that, given an index, returns whether or not the item at that index is loaded.
loadMoreItems A function to be invoked when more data needs to be loaded. It takes a start and stop index and fetches the rows between the start and the stop.
rowCount The number of rows loaded.
children A render props that takes 2 arguments (onItemsRendered and ref). The render prop should call the onItemsRendered argument whenever the rendered section changes.

The child component should call onItemsRendered with information about the new section whenever the user scrolls to a new section of the screen. InfiniteLoader will then use the isItemLoaded method to determine if all the rows in the new section are already loaded.

If the isItemLoaded function returns false, InfiniteLoader calls the function to load more items. Whether or not an item is loaded for a specific index could be determined by, say, checking if the array of data fetched contains a row at that index.

If the data is not yet loaded, InfiniteLoader fetches the new data. The loadMoreItems function decides how the new data is fetched. In my current project, InfiniteLoader fetches data from the server in batches. The loader function provides an offset parameter corresponding to the start index with each request to the server.  In the demo project below, the data is “fetched” from a random data generator and then stored in state.

2. Grid or List: Displaying the Data

You have the data; now you need to display it. The main API react-window options to render data are List and Grid.

  • List renders a windowed list of elements in rows, but only the rows necessary to fill itself based on its horizontal scroll positions.
  • Grid renders a windowed grid of elements. It renders the cells to fill itself based on the horizontal and vertical scroll position.

Grid allows you to window both vertically and horizontally, meaning you can just-in time-render cells in both directions. List only allows you to window vertically.

List and Grid both take:

Property Description
onItemsRendered Callback invoked with information about the section of the list/grid that was rendered. It gets called whenever the user scrolls around the list or the grid. If you are using InfiniteLoader, this is where you should call the loader’s onItemsRendered function. That way, InfiniteLoader can check and see if it needs to fetch more data for the newly-rendered section.
children A render prop that, given a row index (in the case of a list) or both a row index and column index (in the case of a grid), determines what to render.

If you are using InfiniteLoader, you should call its onItemsRendered function whenever the user scrolls to a new section of the grid. To do this, call InfiniteLoader’s onItemRendered function from the grid’s onItemsRendered function:


onItemsRendered={({
            visibleRowStartIndex,
            visibleRowStopIndex,
            overscanRowStopIndex,
            overscanRowStartIndex,
          }) => {
            onItemsRendered({
              overscanStartIndex: overscanRowStartIndex,
              overscanStopIndex: overscanRowStopIndex,
              visibleStartIndex: visibleRowStartIndex,
              visibleStopIndex: visibleRowStopIndex,
            });
          }}

That way, InfiniteLoader can check and see if it needs to fetch more data for the newly-rendered section.

What you decide to render at a given row index and column index of a Grid is completely up to you. For instance, nothing would stop you from ignoring the row and column index parameters in the render prop and just rendering static content in every cell.

In my current project, we are fetching data with InfiniteLoader and then passing that data to react-table, a completely separate library. React-table transforms the data to easily indexable row and column data. Our cell component then uses the row and column index to determine which react-table row and column to render.

In the demo below, the row index is used to index into the row of fetched data, and the column index determines which part of the row to render.

Demo

This demo illustrates data fetching with InfiniteLoader and windowing with react window’s Grid component. When “fetching” data, the demo simulates fetching data from a server by generating fake data with an arbitrary delay.

The demo uses the Grid component to render the data; it takes an optional itemData prop that takes contextual data to be passed to the child render prop (in this case, the gird cell) as a data prop.  Basically, if there is data you need when rendering the grid cells, passing that data into itemData lets you use that data when rendering an individual grid cell. Most likely, the data you want to pass down is the data fetched by InfiniteLoader.

The Grid’s child component determines what to render based on the row index, column index, and the data prop from the Grid. The component uses the data prop to access the loaded rows. It then uses the row index to index into the rows and the column index to determine which attribute of the data to render.

Checkout the demo on code sandbox or github.


This is the first post in a series on creating a scrollable grid with just-in-time data loading:

  1. Using React Window
  2. Storing and Restoring Scroll Position with React Window
  3. Using React Table with React Window
  4. Back-End Implementation (Coming Soon)