2 Comments

Scrollable Grid with Just-in-Time Loading – Part 3: Using React Table with React Window

In the first part of this series, I described how to just-in-time load and incrementally render chunks of data. I used React Window, which can render data in windowed chunks, reducing the initial load time and the amount of memory used to store DOM nodes.

React Window is often used with a data fetching library like InfiniteLoader, which fetches a chunk of data to render in cells. This data is usually formatted as a list of objects, not as table rows and columns. You have to do some additional logic when mapping the fetched data to a React Window Grid cell.

Fortunately, React Table can transform that chunk of data into a shape more easily consumable by React Window’s Grid/List components.

How React Table Works

React Table is a “headless” UI library that can transform data into an easily indexable structure of rows and columns. It separates the logic of generating rows, columns, and headers from the visual representation of those rows and columns. It provides a useTable hook that — given some data, columns, and configuration options — returns everything you need to build a table, like rows and columns.

These pre-computed rows and columns are easy to use when rendering the content of a Grid cell component. The Grid cell can easily index into the rows/columns when determining what content to render for a given row/column index.

React Window + React Table

Let’s say the data for an infinitely loading list is being fetched with React Window InfiniteLoader. (Read the first post in this series for how to hook up InfiniteLoader.) It’s InfiniteLoader’s responsibility to fetch new data whenever necessary. It’s then the responsibility of React Table to translate the newly fetched data into an easy-to-render structure of rows, columns, etc.

When rendering a Grid cell, React Window’s Grid component calls the Grid’s cell render prop with each row index and column index to render. Without React Table, it’s up to each individual Grid cell to use that row index, column index, and not-transformed row data to render the right thing.


export const Item: React.FunctionComponent<Omit<GridChildComponentProps, "data"> & {
  data: ItemData
}>  = props => {
    const {rowIndex, columnIndex, data, style} = props;
    const {isItemLoaded, items} = data
    let content;
    if (!isItemLoaded(rowIndex)) {
      content = "Loading...";
    } else {
      switch (columnIndex) {
        case 0:
          content = items[rowIndex].firstName;
          break;
        case 1:
          content = items[rowIndex].lastName;
          break;
        case 2:
          content = items[rowIndex].suffix;
      
    }
  }
   return <div style={style}>{content}</div>;

}

Rendering the correct data gets easier when React Table transforms the data. Whenever InfiniteLoader fetches new row data, the data can be passed to React Table’s useTable hook to be translated into rows and columns. In this example, columns defines the shape of the columns and items is the row data coming from InfiniteLoader.


  const { headers, rows, prepareRow } = useTable({
    data: items,
    columns: columns,
  });

The rows generated by React Table can be used by the Grid render prop. The renderer can use the row and column index to access the correct row and column, like so:


export const Item: React.FunctionComponent<
  Omit<GridChildComponentProps, "data"> & {
    data: ItemData;
  }
> = (props) => {
  const { rowIndex, columnIndex, data, style } = props;
  const { rows, prepareRow } = data;
  // get row
  const row = rows[rowIndex];
  if (!row) {
    return <div style={style}> LOADING... </div>;
  }

  if (!row.getRowProps) {
    prepareRow(row);
  }

  const column = row.cells[columnIndex];

  return <div {...column.getCellProps({ style })}>{column.render("Cell")}</div>;
};

The code for this example is on GitHub.

What’s Next?

Now that we have React Table, InfiniteLoader, and React Window all hooked up, the front-end portion is pretty much complete. Next time, I’ll dive into the back-end implementation of just-in-time data loading. We’ll finally be replacing our randomly generated client-side data with data coming from a GraphQL server.

This is the third 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)