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>
);
}
}
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>