React-virtualized: List doesn't update display or row heights when data updated.

Created on 7 Nov 2018  路  9Comments  路  Source: bvaughn/react-virtualized

Setup
I have this List that uses CellMeasurer to compute row heights dynamically.

<WindowScroller ref={ref => (this.windowScroller = ref)}>
  {({ height, isScrolling, onChildScroll, scrollTop }) => (
    <AutoSizer disableHeight={true}>
      {({ width }) => (
        <div>
          <List
            autoHeight={true}
            height={height}
            isScrolling={isScrolling}
            onScroll={onChildScroll}
            scrollTop={scrollTop}
            rowCount={contacts.length}
            rowHeight={this._cache.rowHeight}
            rowRenderer={this._rowRenderer}
            width={width}
            deferredMeasurementCache={this._cache}
            overscanRowCount={0}
          />
        </div>
      )}
    </AutoSizer>
  )}
</WindowScroller>

Problem
The list doesn't update either display or row heights when data has changed. For the list to change, I have to scroll the window manually. I've tried to clear the cache, updatePosition and forceRender but still has no effect.

Temporary Solution
Since scrolling to update isn't acceptable, I created a hack in the componentDidUpdate to be able to update the list when data changes.

/**
   * Since virtual list doesn't update till it scrolls, programmatically scroll window to
   * trigger update.
   */
  public componentDidUpdate(prevProps: Props) {
    const becameVisible = this.props.isVisible && !prevProps.isVisible;
    const contactChanged = this.props.contacts !== prevProps.contacts;

    if (this.windowScroller && (becameVisible || contactChanged)) {
      this._cache.clearAll();
      this.windowScroller!.updatePosition();

      window.scrollTo(window.scrollX, window.scrollY + 5);
      window.scrollTo(window.scrollX, window.scrollY - 5);
    }
  }

Most helpful comment

You can add ref

       <List
           ref={this.bindListRef}
            autoHeight={true}
            height={height}
            isScrolling={isScrolling}
            onScroll={onChildScroll}
            scrollTop={scrollTop}
            rowCount={contacts.length}
            rowHeight={this._cache.rowHeight}
            rowRenderer={this._rowRenderer}
            width={width}
            deferredMeasurementCache={this._cache}
            overscanRowCount={0}
          />

the bindListRef function

  bindListRef = ref => {
    this.list = ref;
  };

At last, call forceUpdateGrid in ComponentDidUpdate

  componentDidUpdate() {
    if (this.list) {
      this.list.forceUpdateGrid();
    }
  }

All 9 comments

I have the same problem using ReactVirtualized.Grid. I've found that even when using unique keys, the grid display won't update unless the number of rows is different. Example code at https://codesandbox.io/s/3qlqz9o8nq

I tried ref.forceUpdate() but it only works first time:

this.setState({selectedIndex:index}, ()=>list.forceUpdate())

function({ index, key, style }) { const selected = index===this.state.selectedIndex; return ( <div className={cx("sitcontrol_list_item", selected && "sitcontrol_selected")} data-index={index} key={key} style={style}>{this.props.data[index]}</div> ); }

I made it work rebinding item renderer before setState:

this.itemRenderer = this.itemRedenrer.bind(this)

Inline arrow function may work too

You can add ref

       <List
           ref={this.bindListRef}
            autoHeight={true}
            height={height}
            isScrolling={isScrolling}
            onScroll={onChildScroll}
            scrollTop={scrollTop}
            rowCount={contacts.length}
            rowHeight={this._cache.rowHeight}
            rowRenderer={this._rowRenderer}
            width={width}
            deferredMeasurementCache={this._cache}
            overscanRowCount={0}
          />

the bindListRef function

  bindListRef = ref => {
    this.list = ref;
  };

At last, call forceUpdateGrid in ComponentDidUpdate

  componentDidUpdate() {
    if (this.list) {
      this.list.forceUpdateGrid();
    }
  }

@superman66 It works! Thank you for solving my problem.

for me this didn't work, so I dynamically set an "artificial" key on the List, and I bump it by 1 to programmatically force a rerender

class MyComponent extends React.Component {
  this.listKey = 0

  componentDidUpdate() { this.listKey += 1 }
  ...

  render() {
    <List key={this.listKey} some more props here />
  }
}

Similar problem here, and when my data updated, row height which should also change doesn't update.
Solve this by

componentDidUpdate() {
    if (this.list) {
      this._cache.clearAll(); // re-calculate cache & rowHeight
      this.list.forceUpdateGrid(); 
    }
  }

@y2x33 Use react-virtualized's CellMeasurer and pass your element the measure function. From inside the component, when you need to re-measure you can do so for just that one row.

e.g.

  componentDidUpdate(pProps) {
    if (this.props.text !== pProps.text) {
      this.props.measure();
    }
  }

Or you can do this for when dynamic content loads in:

  handleImageLoad = () => {
    // an image has loaded, probably changing the dimensions of this virtualized item!
    this.props.measure();
  }

  render() {
    const { image } = this.props;

    return (
      <div>
        <p>Some other content that shows up first</p>
        <img src={image.src} onload={this.handleImageLoad} />
      </div>
  }

passing in an anonymous or a no-op function () => {} to onRowsRendered prop fixes this problem

Was this page helpful?
0 / 5 - 0 ratings