facebookarchive / fixed-data-table

A React table component designed to allow presenting thousands of rows of data.
http://facebook.github.io/fixed-data-table/
Other
4.3k stars 553 forks source link

Best practices when setting row height #296

Open aybmab opened 8 years ago

aybmab commented 8 years ago

Are there any suggestions on how to go about creating a rowHeightGetter function?

3cooper commented 8 years ago

I am interetested in this as well. This seems to be a similar discussion - https://github.com/facebook/fixed-data-table/issues/322 - but it doesn't have any answers yet.

danbovey commented 8 years ago

Posting this here because it seems to be the most general issue surrounding custom row height.

Using ideas from react-measure and a custom prop on the Table called flexibleRows, I got quite far creating a grid with completely variable rowHeight measured from the row's own contents.

The initial table looks great but I got stuck when I realised FDT is reusing elements/not rendering rows outside of the viewport/some other magic! - so to measure rows again when they have new contents I've either got to force an update when you scroll or measure every time the cell updates with componentDidUpdate - either way is really laggy.

Any advice on this would be really welcome, as it's a lot of work for nothing, even though in the end having to implement this feature is very avoidable and I'm probably going to skip having dynamic content in my tables.

Callback function for when a cell is measured

handleCellMeasure(rowIndex, dimensions) {
    let rowHeights = this.state.rowHeights;

    // Only store the largest cell height of a row
    if(typeof rowHeights[rowIndex] == 'undefined' || rowHeights[rowIndex] < dimensions.height) {
        rowHeights[rowIndex] = dimensions.height;

        this.setState({
            rowHeights: rowHeights
        });
    }
}

The rowHeightGetter

Any time the grid asks for a row height, check if we've stored it's measurements in state

rowHeightGetter(index) {
    let rowHeight = 37; // Set the default rowHeight

    if(this.props.flexibleRows) {
        let calculatedHeight = this.state.rowHeights[index]; // Retreive the rowHeight stored in state
        if(typeof calculatedHeight != 'undefined') {
            rowHeight = calculatedHeight + 16; // 8px padding
        }
    }

    return rowHeight;
}

render

Then in the render of my Grid I am pushing my custom props into FDT components and I wrap the contents cell in a Measuring component.

<Table ...>
    {React.Children.map(this.props.children, el => {
        if(flexibleRows) {
            clonedCell = React.createElement(MeasureCell, {
                onMeasure: this.handleCellMeasure.bind(this)
            }, clonedCell);
        }
        // Many more extensions on the column and the header
        return React.createElement(Column, {
            ...props, // Props to pass through to the real column (injecting the data, also - which is neat)
            cell: clonedCell,
            header: headerCell
        });
    })}
</Table>

MeasureCell

The measure cell is just a component that wraps the cell and measures the height of the element.

// Find the cell contents and measure it
const dimensions = this._node.querySelector('.public_fixedDataTableCell_cellContent').getBoundingClientRect(); 
if(dimensions.height > 0) {
    this.props.onMeasure(this.props.rowIndex, dimensions); // Run the callback function seen above
}