Search code examples
javascriptreactjsreact-virtualized

Building a responsive infinite scroll table with InfiniteLoader, Table, Column, AutoSizer, and CellMeasurer


I've just finished a working table with using InfiniteLoader, Table, Column, and AutoSizer, and I realize this table does not scale horizontally with the browser window. It took me a long time to get this working despite much of the library being well documented. What I want to achieve is a HTML like table that resizes based on the browser window's width, grows row heights vertically as content wraps, and uses infinite load with varying row heights. Are all of these points possible with react-virtualized? It seems best to ask first as combining all the moving parts of this library can be tricky. Thanks!

Here is my component:

import React from 'react';
import fetch from 'isomorphic-fetch';
import { InfiniteLoader, Table, Column, AutoSizer } from 'react-virtualized';
import { connect } from 'react-redux';
import { CSSTransitionGroup } from 'react-transition-group';
import { fetchProspects } from '../actions/prospects';
import Footer from './footer';

class ProspectsTable extends React.Component {

  constructor(props, context) {
    super(props, context);
    this.renderProspects = this.renderProspects.bind(this);
    this.isRowLoaded = this.isRowLoaded.bind(this);
    this.loadMoreRows = this.loadMoreRows.bind(this);
    this.rowRenderer = this.rowRenderer.bind(this);
    this.state = {
      remoteRowCount: 200,
      list: [
        {
          fullName: '1 Vaughn',
          email: '[email protected]',
          phone: '608 774 6464',
          programOfInterest: 'Computer Science',
          status: 'Active',
          dateCreated: '10/31/2017',
        },
        {
          fullName: '2 Vaughn',
          email: '[email protected]',
          phone: '608 774 6464',
          programOfInterest: 'Computer Science',
          status: 'Active',
          dateCreated: '10/31/2017',
        },
        {
          fullName: '3 Vaughn',
          email: '[email protected]',
          phone: '608 774 6464',
          programOfInterest: 'Computer Science',
          status: 'Active',
          dateCreated: '10/31/2017',
        },
      ],
    };
  }

  isRowLoaded({ index }) {
    return !!this.state.list[index];
  }

  loadMoreRows({ startIndex, stopIndex }) {
    return fetch(`http://localhost:5001/api/prospects?startIndex=${startIndex}&stopIndex=${stopIndex}`)
      .then((response) => {
        console.log('hi', response);
        console.log('hi', this.props);
      });
  }

  rowRenderer({ key, index, style }) {
    return (
      <div
        key={key}
        style={style}
      >
        {this.state.list[index]}
      </div>
    );
  }

  render() {
    return (
            <InfiniteLoader
              isRowLoaded={this.isRowLoaded}
              loadMoreRows={this.loadMoreRows}
              rowCount={this.state.remoteRowCount}
            >
              {({ onRowsRendered, registerChild }) => (
                <div>
                  <AutoSizer>
                    {({ width }) => (
                      <Table
                        ref={registerChild}
                        onRowsRendered={onRowsRendered}
                        width={width}
                        height={300}
                        headerHeight={20}
                        rowHeight={30}
                        rowCount={this.state.list.length}
                        rowGetter={({ index }) => this.state.list[index]}
                      >
                        <Column
                          label="Full Name"
                          dataKey="fullName"
                          width={width / 6}
                        />
                        <Column
                          width={width / 6}
                          label="Email"
                          dataKey="email"
                        />
                        <Column
                          width={width / 6}
                          label="Phone"
                          dataKey="phone"
                        />
                        <Column
                          width={width / 6}
                          label="Program of Interest"
                          dataKey="programOfInterest"
                        />
                        <Column
                          width={width / 6}
                          label="Status"
                          dataKey="status"
                        />
                        <Column
                          width={width / 6}
                          label="Date Created"
                          dataKey="dateCreated"
                        />
                      </Table>
                    )}
                  </AutoSizer>
                </div>
              )}
            </InfiniteLoader>
      );
    }
  }
}

const mapStateToProps = state => ({
  prospects: state.prospects,
});

export default connect(mapStateToProps)(ProspectsTable);

Here is a plnkr that closely resembles my code, I referenced this a lot: https://plnkr.co/edit/lwiMkw?p=preview


Solution

  • Great work so far on an ambitious project. A couple thoughts on achieving your remaining objectives:

    1. Resizes based on the browser window's width

      Try Putting your AutoSizer component at the top level of this hierarchy, and ensure its parent component's width changes with the viewport. This could be achieved as simply as a parent div with width: 100vw.

    2. grows row heights vertically as content wraps, and uses infinite load with varying row heights

      These can both be achieved by dynamically setting your row heights based on data. From the docs on the rowHeight prop for Table:

      Either a fixed row height (number) or a function that returns the height of a row given its index: ({ index: number }): number

      React-Virtualized does its magic by calculating its dimensions ahead of time, so you're not going to be able to get flex-wrap functionality or other CSS-based solutions to work correctly. Instead, determine how you can use the data for a given row (and potentially the current screen dimensions) to calculate what the height should be for that row. For example:

      const BASE_ROW_HEIGHT = 30;
      const MAX_NAME_CHARS_PER_LINE = 20;
      
      ...
      
        getRowHeight = ({ index }) => {
          const data = this.state.list[index];
      
          // Not a great example, but you get the idea;
          // use some facet of the data to tell you how
          // the height should be manipulated
          const numLines = Math.ceil(fullName.length / MAX_NAME_CHARS_PER_LINE);
      
          return numLines * BASE_ROW_HEIGHT;
        };
      
        render() {
          ...
      
          <Table
            ...
            rowHeight={this.getRowHeight}
          >      
        }
      

    For additional reference, I found the source code for the React-Virtualized Table example quite informative - especially the lines regarding dynamically setting row height:

    _getRowHeight({ index }) {
      const { list } = this.context;
    
      return this._getDatum(list, index).size;
    }
    

    Best of luck!