Overlap of elements when using dynamic height List

I have almost the same problem as Facebook has when rendering it's feed. As soon as a lot of posts are rendered, performance problems appear. After some research I found react-virtualized, which is awesome but I am having trouble to make it work for my app.

These are the problems that I am facing:

  1. Since each post can have an iframe embedded, or an image, I am firing the measure callback from the CellMeasurer once these items are loaded. Because of this, some items seem misaligned. I tried doing parent.measureAllRows() and parent.recomputeRowHeights() each time the measure callback is called to see if it would fix it but it doesn't.
  2. Each post has an expandable section, therefore I need to recalculate it's height. Is there any alternative aside from sending the props to the component?

This is the setup:

class VirtualPostList extends React.PureComponent {
  constructor(props, context) {
    super(props, context);
    this._cache = new ReactVirtualized.CellMeasurerCache({
      fixedWidth: true,
      defaultHeight: 400
    });
    this.rowRenderer = this.rowRenderer.bind(this);
  }


  rowRenderer({index, isScrolling, key, parent, style}) {
    return (
      <ReactVirtualized.CellMeasurer
        cache={this._cache}
        columnIndex={0}
        key={key}
        parent={parent}
        rowIndex={index}>
        {({measure}) => (
          <div key={key} style={style}>
            <Post onLoad={measure}/>
          </div>
        )}
      </ReactVirtualized.CellMeasurer>
    )
  }

  componentDidUpdate(){
    this.listComponent.recomputeRowHeights();
  }

  render() {
    const cache = this._cache;
    const rowCount = this.props.posts.length;
    const _this = this;
    return (
      <ReactVirtualized.WindowScroller>
        {({height, isScrolling, onChildScroll, scrollTop}) =>
          <ReactVirtualized.AutoSizer disableHeight>
            {({width}) =>
              <ReactVirtualized.List
                autoHeight
                isScrolling={isScrolling}
                height={height}
                width={width}
                rowCount={rowCount}
                deferredMeasurementCache={cache}
                rowHeight={cache.rowHeight}
                rowRenderer={this.rowRenderer}
                scrollTop={scrollTop}
                onScroll={onChildScroll}
                ref={(listComponent) => _this.listComponent = listComponent}
              />
            }
          </ReactVirtualized.AutoSizer>}
      </ReactVirtualized.WindowScroller>
    )
  }
}

Example of overlap:

enter image description here

Upvotes: 3

Views: 3160

Answers (1)

As @brianvaughn suggested, I wasn't calling the measure method in every place I should. These became a bit boilerplate and hard to mantain since elements not only have images but they can expand or contract themselves.

Since I wanted to avoid manually calling measure, I attached a ResizeObserver to the component just like ReactMeasure does. After that, I changed the rowRenderer function to this:

  rowRenderer({index, isScrolling, key, parent, style}) {
    return (
      <ReactVirtualized.CellMeasurer
        cache={this._cache}
        columnIndex={0}
        key={key}
        parent={parent}
        rowIndex={index}>
        {({measure}) => (
          <div key={key} style={style}>
            <ResizeObservable onResize={measure}>
              <Post/>
            </ResizeObservable>
          </div>
        )}
      </ReactVirtualized.CellMeasurer>
    )
  }

As you see, the measure function is called each time a resize happens. This also includes when images or iframes are loaded so no need to manually call them

Upvotes: 1

Related Questions