I have a "card" component which presents data for an object in a set way.
These card components have a pre-defined width and height.
These card components will be presented in a list, and I would like that list to make use of the horizontal space available.
I would like the number of columns to change responsively. e.g. if the parent component becomes too narrow for 3 columns then it should drop to using 2 columns.
Conceptually, this is fairly easy to achieve. The component sizes are well known so there's no need for on-the-fly size measurements, and it's trivial to calculate the number of components that can fit in the width of the list, which gives the number of columns.
There's even the option to use display:flex
and have it do the calculations and layout for you.
I'm currently trying to use react-virtualized
to achieve this responsive-column list as we're already using it in other situations in our app. It's support for virtualization is also a strong attractor.
The masonry layout appeared to provide what we want, but I "can't get it to work".
It requires a CellMeasurerCache
which needs either a fixed width, or a fixed height. That is unfortunate since my components have both a fixed width and height, and so the measurements are unnecessary.
I used a fixedWidth
for the CellMeasurerCache
as the cellPositioner
requires a columnWidth
and I assumed it'd be better for it to think that'll not need to change.
Here's my code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { RouteComponentProps } from 'react-router-dom';
import { AutoSizer, Masonry, WindowScroller, CellMeasurer, CellMeasurerCache, MasonryCellProps, createMasonryCellPositioner } from 'react-virtualized';
import { Task } from '../../store/tasks';
import { TaskCard } from './taskcard';
type TaskCardListProps = {
scrollElement?: any;
tasks: Task[];
};
type TaskCardListState = {};
export class TaskCardList extends React.Component<TaskCardListProps, TaskCardListState> {
public render() {
const { tasks, scrollElement } = this.props;
const cache = new CellMeasurerCache({
defaultHeight: 120,
defaultWidth: 400,
fixedWidth: true
});
const cellPositioner = createMasonryCellPositioner({
cellMeasurerCache: cache,
columnCount: 2,
columnWidth: 400,
spacer: 10
});
const cellRenderer = (props: MasonryCellProps) => {
const { index, key, parent, style } = props;
const task = tasks[index];
return (
<CellMeasurer cache={cache} index={index} key={key} parent={parent}>
<TaskCard { ...task } />
</CellMeasurer>
)
};
return (
<Masonry
autoHeight={false}
cellCount={tasks.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={cellRenderer}
height={1000}
width={1500}
/>
);
}
}
What I found was that it did render the cards, but there was only a single column, and the masonry component applied height:auto;
as a style to the card component element! That meant it over-rode my fixed height causing the cards to become miss-shaped.
I'm not sure why it only renders a single column, and not two of them.
The .ReactVirtualized__Masonry
element has a height of 1000px, as set on the Masonry
component, but it's child, ReactVirtualized__Masonry__innerScrollContainer
, has a height of 335px;
, and I'm not sure why, but it' "cuts off" the cards that can be seen.
Am I doing something obviously wrong with Masonry?
Is Masonry the wrong choice, and there's a better option within react-virtualized
?
Am I trying to use a hammer to screw in a nail, and should I just make something from scratch?