How to set up dynamic row height in react-virtualized List?

21k views Asked by At

I have gone through many answers on StackOverflow. I have also gone through List document here, react-virtualized/List. But, still I am not able to understand how to dynamically set row height in react-virtualized List. How to calculate the height in rowHeight prop function?

I can call my function like rowHeight={({ index }) => this.computeRowHeight({ index })}. But how will the function compute the row height?

Following is the code for reference.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { AutoSizer, InfiniteLoader, List } from 'react-virtualized';
import _ from 'lodash';

class InfiniteList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: props.list,
      loading: false
    };
  }
  componentDidMount() {
    fetch('http://jsonplaceholder.typicode.com/comments')
      .then((response) => {
        response.json().then((data) => {
          this.setState({
            list: _.concat(this.state.list, _.map(data, 'body')),
            loading: false
          });
        });
      });
  }
  isRowLoaded({ index }) {
    return !!this.state.list[index];
  }

  loadMoreRows({ startIndex, stopIndex }) {
    if (this.state.loading) {
      return;
    }
    this.setState({
      loading: true
    });
    return fetch('http://jsonplaceholder.typicode.com/comments')
      .then((response) => {
        response.json().then((data) => {
          // Simulate delay
          setTimeout(() => {
            this.setState({
              list: _.concat(this.state.list, _.map(data, 'body')),
              loading: false
            });
          }, 3000);
        });
      });
  }

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

  render() {
    return (
      <div>
        <InfiniteLoader
          isRowLoaded={({ index }) => this.isRowLoaded({ index })}
          loadMoreRows={({ startIndex, stopIndex }) => this.loadMoreRows({ startIndex, stopIndex })}
          rowCount={this.state.list.length}
        >
          {({ onRowsRendered, registerChild }) => (
            <AutoSizer disableHeight>
              {({ width }) => (
                <List
                  onRowsRendered={onRowsRendered}
                  ref={registerChild}
                  width={width}
                  height={320}
                  rowCount={this.state.list.length}
                  rowHeight={40}
                  rowRenderer={({ key, index, isScrolling, isVisible, style }) => this.rowRenderer({ key, index, isScrolling, isVisible, style })}
                />
              )}
            </AutoSizer>
          )}
        </InfiniteLoader>
        {this.state.loading && <p>Loading...</p>}
      </div>
    );
  }
}

const list = [];

ReactDOM.render(
  <InfiniteList list={list} />,
  document.querySelector('#root')
);

Update

Dynamic height is now working with the following code with CellMeasurer. But, unfortunately this.loadMoreRows() function is not called in InfiniteLoader. Without CellMeasurer also it is not working. I am not sure what I did wrong in the following code.

import React, { Component } from 'react';
import { AutoSizer, CellMeasurer, InfiniteLoader, List } from 'react-virtualized';
import _ from 'lodash';

class InfiniteList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: props.list,
      loading: false
    };
  }
  componentDidMount() {
    fetch('http://jsonplaceholder.typicode.com/comments')
      .then((response) => {
        response.json().then((data) => {
          this.setState({
            list: _.concat(this.state.list, _.map(data, 'body')),
            loading: false
          });
        });
      });
  }
  isRowLoaded({ index }) {
    return !!this.state.list[index];
  }

  loadMoreRows({ startIndex, stopIndex }) {
    if (this.state.loading) {
      return;
    }
    this.setState({
      loading: true
    });
    return fetch('http://jsonplaceholder.typicode.com/comments')
      .then((response) => {
        response.json().then((data) => {
          // Simulate delay
          setTimeout(() => {
            this.setState({
              list: _.concat(this.state.list, _.map(data, 'body')),
              loading: false
            });
          }, 3000);
        });
      });
  }

  rowRenderer({ key, index, isScrolling, isVisible, style }) {
    if (isVisible) {
      return (
        <div key={key} style={style}>#{index} {this.state.list[index]}.</div>
      );
    }
  }
  cellRenderer({ columnIndex, key, rowIndex, style }) {
    return (
      <div
        key={key}
        style={style}
      >
        <div>#{rowIndex} {this.state.list[rowIndex]}.</div>
      </div>
    );
  }
  render() {
    return (
      <div>
        <InfiniteLoader
          isRowLoaded={isRowLoaded => this.isRowLoaded(isRowLoaded)}
          loadMoreRows={loadMoreRows => this.loadMoreRows(loadMoreRows)}
          rowCount={this.state.list.length}
        >
          {({ onRowsRendered, registerChild }) => (
            <AutoSizer disableHeight>
              {({ width }) => (
                <CellMeasurer
                  cellRenderer={cellRenderer => this.cellRenderer(cellRenderer)}
                  columnCount={1}
                  rowCount={this.state.list.length}
                >
                  {({ getRowHeight }) => (
                    <List
                      onRowsRendered={onRowsRendered}
                      ref={registerChild}
                      width={width}
                      height={400}
                      rowCount={this.state.list.length}
                      rowHeight={getRowHeight}
                      rowRenderer={rowRenderer => this.rowRenderer(rowRenderer)}
                    />
                  )}
                </CellMeasurer>
              )}
            </AutoSizer>
          )}
        </InfiniteLoader>
        {this.state.loading && <p>Loading...</p>}
      </div>
    );
  }
}

const list = [];

ReactDOM.render(
  <InfiniteList list={list} />,
  document.querySelector('#root')
);

Any help will be appreciated. Thanks!

2

There are 2 answers

4
Drew Schuster On

You need to write computeRowHeight yourself to determine the height of the row. In some instances, based on the index and the properties of that row you might be able to know what the row's height will be. For example, if your rows might be different components, if you know a certain row will be component A with height 40 and another row will be component B with height 60, you can check those props and return the right height.

If you don't know the heights, then you're going to have to actually look at the element in the DOM and figure out it's height. If it doesn't yet exist in the DOM, you'll need to return a best guess initially and then call recomputeRowHeights after render and inspect the DOM then.

0
Naeem Baghi On

I went through all the things you and other people mentioned, and finally, I ended up creating this Example which I explained here too. It is using Table beside react hooks which may be helpful to some other people too. I build this one for some table which may have some expandable rows.