Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I tell the Masonry component that its cells have changed their height?

I have a react component that displays a one-dimensional list of items in a grid pattern. Every item has the same width and height, and they are located within three columns. There can be a lot of these, so I decided to give react-virtualized a whirl, using the Masonry component, which seems to have been written for precisely this use case. It works well, but for one caveat: If the items change their height, I cannot get Masonry to notice.

Here's a reduced example:

constructor(props) {
    [...]
    this.cellMeasurerCache = new CellMeasurerCache({
        defaultHeight: this.state.height,
        fixedWidth: true,
    });

    this.cellPositioner = createMasonryCellPositioner({
        cellMeasurerCache: this.cellMeasurerCache,
        columnCount: 3,
        columnWidth: 250,
        spacer: 20,
    });
}

[...]

cellRenderer({ index, key, parent, style }) {
    return (
        <CellMeasurer
            cache={this.cellMeasurerCache}
            index={index}
            key={key}
            parent={parent}
        >
            <div style={style}>
                [...]
            </div>
        </CellMeasurer>
    );
}

resetMasonry() {
    this.cellMeasurerCache.clearAll();

    this.cellPositioner.reset({
        cache: this.cellMeasurerCache,
        columnCount: 3,
        columnWidth: 250,
        spacer: 20,
    });

    this.masonryRef.recomputeCellPositions();
}

render() {
    return (
        <AutoSizer>
            {({ height, width }) =>
                <Masonry
                    cellCount={this.state.list.length}
                    cellMeasurerCache={this.cellMeasurerCache}
                    cellPositioner={this.cellPositioner}
                    cellRenderer={this.cellRenderer}
                    height={height}
                    ref={this.setMasonryRef}
                    width={width}
                />
            }
        </AutoSizer>
    );
}

resetMasonry is called when the component's height state has changed. I have cargo-culted the current code from several stackoverflow answers and other resources, even went over the source code, but nothing works. I've noticed that I'm not telling the cellMeasurerCache or anything else about the height change, so I really shouldn't expecting it to work, but there seems to be no way to get that information in there, not even by instantiating a new CellMeasurerCache.

Incidentally, if I change the columnWidth within cellPositioner.reset, the Masonry component updates accordingly, sure enough.

Does anybody know what I'm missing to get it working for a height change? Thanks!

like image 324
Martin Denk Avatar asked Jan 04 '23 21:01

Martin Denk


1 Answers

If your Masonry cell's sizes change, you need to clear the cached sizes in your CellMeasurerCache and then make sure the cellPositioner is aware of the new sizes also. For example:

// Clear cached measurements since sizes have changed:
cellMeasurerCache.clearAll()

// Let your component know it needs to re-render
this.setState({...}, () => {
  // Assuming you're using the default createCellPositioner()
  // Let it know of any values that may have changed:
  cellPositioner.reset({
    columnCount,
    columnWidth,
    spacer
  })

  // Tell Masonry to discard any cached position data:
  masonryRef.clearCellPositions()
})

You can see a demo of a Masonry component with changing column heights on the RV site just by changing the column width. The source code for that demo is available in the GitHub repo also.

From what you're describing though, I wouldn't suggest using Masonry. I would actually just suggest using a List in a similar way to how I did in this example: http://plnkr.co/edit/zjCwNeRZ7XtmFp1PDBsc?p=preview

The key bit is to calculate the number of items per row dynamically based on the available width, and then you give List an adjusted rowCount:

  const itemsPerRow = Math.floor(width / ITEM_SIZE);
  const rowCount = Math.ceil(ITEMS_COUNT / itemsPerRow);
like image 110
bvaughn Avatar answered May 16 '23 06:05

bvaughn