4

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.

HTML Screenshot

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?

1 Answers1

8

These card components have a pre-defined width and height.

Are all cards the same width and height? If so, you don't need the Masonry component for this. It's intended for use-cases like pinterest.com. (I specifically built it for them actually.)

What you're describing is less complex and so can be done in a more efficient way. Here's an example of using List to build the kind of UI you've described.

If each card is a different size, then Masonry is the correct component to use.

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.

Unfortunately this is true. I built the Masonry component specifically for Pinterest's use-case and tried to optimize it as much as I could to be fast rather than generic.

bvaughn
  • 13,300
  • 45
  • 46