Skip to content

Instantly share code, notes, and snippets.

@bdkent
Last active February 10, 2019 21:46
Show Gist options
  • Save bdkent/56c1e6275a3a8bc5124af58e61703895 to your computer and use it in GitHub Desktop.
Save bdkent/56c1e6275a3a8bc5124af58e61703895 to your computer and use it in GitHub Desktop.
react-virtualized <AutoSizer> + <Masonry>
var DEFAULT_CELL_HEIGHT = 250;
var DEFAULT_CELL_SPACING = 10;
function createFullWidthMasonry(props) {
var cellSpacing = props.cellSpacing || DEFAULT_CELL_SPACING;
var cellHeight = props.cellHeight || DEFAULT_CELL_HEIGHT;
var keyMapper = props.keyMapper || _.identity();
var elementRenderer = props.elementRenderer || _.constant(null);
function toDynamicCellWidth(width, columns) {
var spacing = (columns - 1) * cellSpacing;
return Math.floor((width - spacing) / columns);
}
return React.createClass({
propTypes: {
elements: React.PropTypes.array.isRequired,
columns: React.PropTypes.number.isRequired
},
getInitialState: function () {
return {
width: null
};
},
componentWillReceiveProps: function (nextProps) {
var self = this;
if (self.props.columns !== nextProps.columns || self.props.elements !== nextProps.elements) {
var width = self.state.width;
if (!_.isNil(width)) {
self._refreshCells(width, nextProps.columns);
}
}
},
_setMasonryRef: function (ref) {
var self = this;
self._masonry = ref;
},
_refreshCells: function (width, columns) {
var self = this;
var columnWidth = toDynamicCellWidth(width, columns);
self.setState({
width: width
});
if (_.isNil(self._cache)) {
self._cache = new CellMeasurerCache({
defaultHeight: cellHeight,
defaultWidth: columnWidth,
fixedWidth: true
});
self._cellPositioner = createMasonryCellPositioner({
cellMeasurerCache: self._cache,
columnCount: columns,
columnWidth: columnWidth,
spacer: cellSpacing
});
} else {
self._cache._defaultWidth = columnWidth; // HACKCITY
self._cache.clearAll();
self._cellPositioner.reset({
columnCount: columns,
columnWidth: columnWidth,
spacer: cellSpacing
});
self._masonry.clearCellPositions();
}
self.forceUpdate();
},
_onResize: function (props) {
var width = props.width;
var self = this;
self._refreshCells(width, self.props.columns);
},
render: function () {
var self = this;
var elements = self.props.elements;
var cellCount = _.size(elements);
var renderCell = function (props) {
var index = props.index;
var key = props.key;
var style = props.style;
var parent = props.parent;
var element = elements[index];
return (
<CellMeasurer
cache={self._cache}
index={index}
key={key}
parent={parent}
>
<div style={style}>{elementRenderer(element)}</div>
</CellMeasurer>
);
};
return (
<AutoSizer onResize={self._onResize}>
{
(function (props) {
var height = props.height;
var width = props.width;
if (_.isNil(self._cache)) {
return null;
} else {
return (
<Masonry
ref={self._setMasonryRef}
keyMapper={keyMapper}
cellCount={cellCount}
cellMeasurerCache={self._cache}
cellPositioner={self._cellPositioner}
cellRenderer={renderCell}
height={height}
width={width}
/>
);
}
})
}
</AutoSizer>
);
}
});
};
@mark-szabo
Copy link

Thanks, this helped a lot! One critically important thing: using an anonymous (inline) function in AutoSizer is a must as if I use a named function (like 'renderMasonry') AutoSizer will cache it and not re-render it when props/state changes.

For me it was enough to do this in componentWillReceiveProp:

componentWillReceiveProps() {
    if (!this._cellPositioner) return;
    this._cellPositioner.reset({
        columnCount: this._columnCount,
        columnWidth: CARD_WIDTH,
        spacer: CARD_GUTTER,
    });
    this._masonry.recomputeCellPositions();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment