reactjsreact-virtualized

React Virtualized Masonry does not resize with the browser


I am trying to build a feed (a Pinterest-like feed to put it straight). I am using react-virtualized Masonry component.

You can see how the items rearrange and the component is correctly resized when the browser window resizes in this screen recording.

However, mine has a strange behavior as you can see in this screen recording.

Here's the relevant excerpt of my code:

export default class Feed extends Component <PropTypes, State> {
  static readonly defaultProps = {
    enableInfiniteScroll: false,
    chunkSize: 9,
  };

  private _windowScroller: WindowScroller;
  private _masonry: Masonry;
  private _columnCount: number;

  private _cache: CellMeasurerCache;
  private _cellPositioner: Positioner;

  constructor(props: PropTypes) {
    super(props);

    // ...

    this._columnCount = 3;

    this._cache = new CellMeasurerCache({
      defaultWidth: COLUMN_WIDTH,
      defaultHeight: 400,
      fixedWidth: true,
    });
    this._cellPositioner = createMasonryCellPositioner({
      cellMeasurerCache: this._cache,
      columnCount: this._columnCount,
      columnWidth: COLUMN_WIDTH,
      spacer: GUTTER,
    });
  }

  onResize({width}: Size) {
    this._cache.clearAll();
    this.calculateColumnCount(width);
    this.resetCellPositioner();
    this._masonry.recomputeCellPositions();
  }

  cellRenderer(cellProps: MasonryCellProps) {
    const {items} = this.state;
    const listing = items.get(cellProps.index);

    return (
      <CellMeasurer
        cache={this._cache}
        index={cellProps.index}
        key={cellProps.key}
        parent={cellProps.parent}
      >
        <div style={cellProps.style}>
          <ListingCard company={listing} />
        </div>
      </CellMeasurer>
    );
  }

  calculateColumnCount(width: number) {
    this._columnCount = Math.floor((width + GUTTER) / (COLUMN_WIDTH + GUTTER));
  }

  resetCellPositioner() {
    this._cellPositioner.reset({
      columnCount: this._columnCount,
      columnWidth: COLUMN_WIDTH,
      spacer: GUTTER,
    });
  }

  render() {
    const {items, isLoading, hasMore} = this.state;

    return (
      <div className={Styles['listings-feed']}>
        <WindowScroller scrollElement={window} ref={this.setRef}>
          {({height, isScrolling, onChildScroll, scrollTop, registerChild}) => (
            <div className={Styles.windowScrollerContainer}>
              <AutoSizer disableHeight onResize={this.onResize}>
                {({width}) => (
                  <div ref={registerChild as any}>
                    <Masonry
                      cellCount={items.size}
                      cellMeasurerCache={this._cache}
                      cellPositioner={this._cellPositioner}
                      cellRenderer={this.cellRenderer}
                      height={height}
                      width={width}
                      autoHeight
                      ref={(r: Masonry) => this._masonry = r}
                    />
                  </div>
                )}
              </AutoSizer>
            </div>
          )}
        </WindowScroller>
      </div>
    );
  }
}

Solution

  • After testing with different parameters and tweaks, I found out is was not rendering all of the items because they were technically out of range (not in the user's view). They were not out of view when scrolling, it is just that the <Masonry /> component only updates on property changes.

    Since I am using a <WindowScroller /> component, I found out it offers a scrollTop variable for the children function so I passed this directly to the Masonry component:

    <WindowScroller scrollElement={window} ref={this.setRef}>
      {({height, isScrolling, onChildScroll, scrollTop, registerChild}) => (
        <div className={Styles.windowScrollerContainer}>
          <AutoSizer disableHeight onResize={this.onResize}>
            {({width}) => (
              <div ref={registerChild as any}>
                <Masonry
                  cellCount={items.size}
                  cellMeasurerCache={this._cache}
                  cellPositioner={this._cellPositioner}
                  cellRenderer={this.cellRenderer}
                  height={height}
                  width={width}
                  autoHeight
                  ref={(r: Masonry) => this._masonry = r}
                  scrollTop={scrollTop}
                />
              </div>
            )}
          </AutoSizer>
        </div>
      )}
    </WindowScroller>