3

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

  • 1
    Are you sure your `onLoad` callback isn't firing too soon? I'd be happy to take a look at a Plnkr if you'll create a repro. This should work okay. I imagine there's a timing issue of some sort. – bvaughn Jul 28 '17 at 15:09
  • Thanks for answering man. Your library is great. I realized that I am not firing the `onLoad` callback when loading the small avatars. Maybe that might be it. Not sure though. – Matías Hernán García Jul 28 '17 at 19:42
  • It might be related to that but I noticed that the performance significantly decreases with this. Is there an easy way to replace the current CellMeasurer with a component like ReactMeasure? I have found ReactMeasure simpler since it uses a resize observer. I saw that you commented something about this in the github repo. – Matías Hernán García Jul 28 '17 at 20:13
  • You're welcome. :) `CellMeasurer` should be pretty optimized for working with `Grid` so far as I know. Not sure what would make it slower if you triggered the callback at the right time. On the react-virtualized demo page, I am loading images in a `List` with `CellMeasurer` and using `onLoad` to measure them in a similar way and it seems to perform pretty well. – bvaughn Jul 28 '17 at 23:35
  • Thanks for answering again!. I might try to profill the performance issue, but I am noticing the scrolling is slow even when 5 elements of these are present. Probably something on my end. I know that the components I am using are pretty heavy. However, my components can expand themselves, so I am having an issue on how to measure them. Is there a pattern that I can follow aside of firing the measure callback every single time? That's why I brought up ReactMeasure, which listens to the element's resize event and therefore the calls for measure are easier – Matías Hernán García Jul 29 '17 at 21:23
  • Ok, instead of manually calling measure in every place I needed, I attached a resize observer to the div that uses the CellMeasurer styling. Each time a resize happens, a call to the `measure` function is done. I am right now profiling it, it seems that it's a bit less performant than the other option but saves a lot of headaches for now, and it's good enough for my requirements :) – Matías Hernán García Jul 29 '17 at 21:42

1 Answers1

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