bvaughn / react-window

React components for efficiently rendering large lists and tabular data
https://react-window.now.sh/
MIT License
15.72k stars 782 forks source link

How to iterate through grid with variable columnCount? #656

Closed jeffchuber closed 3 weeks ago

jeffchuber commented 2 years ago

I am rendering a list as a grid.

Right now, I have my index to my itemData defined as (rowIndex * 3) + columnIndex - this is stable for columnCount = 3. However I want to make the parent container resizable, and to increase the number of columnCount on resize. That pat is working, but I can't figure out how to pass any info about the current columnCount down to the Cell element. From the code, it looks like this is the only data that is exposed.

https://github.com/bvaughn/react-window/blob/d80bef25fe706d0c73fc801674c086f681811190/src/createGridComponent.js#L15

type RenderComponentProps<T> = {|
  columnIndex: number,
  data: T,
  isScrolling?: boolean,
  rowIndex: number,
  style: Object,
|};

Would it be possible to pass columnCount (and possibly rowCount as well) down to the RenderComponent? This would allow me to do the relevant "math" in the component.

Apologies if I missed something entirely obvious!

Rowansdabomb commented 1 year ago

My solution is to add the columnCount to each item so it's accessible through the props.data.

This is a terrible solution. I'd love if the parent's metadata was forwarded to the render component.

ivan-shaban commented 1 year ago

Why not to pass index prop in to renderer's function? Looks like it is simplest solution which not require additional calculations during rendering and we can access to renderer's data by data[index]

ChaseLewis commented 1 year ago

Here is an example of how this could be done

 import { ComponentType, useCallback, useEffect, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeGrid as _FixedSizeGrid, FixedSizeGridProps } from "react-window";
const FixedSizeGrid = _FixedSizeGrid as ComponentType<FixedSizeGridProps>;

import './dataview.css'

const GUTTER_SIZE = 8;
const ROW_HEIGHT = 250;
const COLUMN_WIDTH = 250;

type CellProps = { columnIndex: number, rowIndex: number, style: React.CSSProperties };

export const GridComponent = () => {
    const [cellData,setCellData] = useState<number[]>([...Array(100).keys()]);

    const Cell = useCallback(({ columnIndex, rowIndex, style, columnCount }: CellProps & { columnCount: number }) => {
        const index = rowIndex * columnCount + columnIndex;
        if(index >= cellData.length) {
            return null;
        }

        return (
        <div style={{
            ...style,
            background: '#333',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            left: style.left as number + GUTTER_SIZE,
            top: style.top as number + GUTTER_SIZE,
            width: style.width as number - GUTTER_SIZE,
            height: style.height as number - GUTTER_SIZE,
        }}>
          <Typography>{cellData[index] + 1}</Typography>
        </div>
      )},[cellData]);

    return (
        <div style={{ width: '100%', height: '100%' }}>
            <AutoSizer style={{ width: '100%', height: '100%' }}>
                {({ height, width }) => {
                    const columnCount = Math.floor(width as number / (COLUMN_WIDTH+GUTTER_SIZE));
                    const rowCount = Math.ceil(cellData.length / columnCount);

                    return (
                    <FixedSizeGrid
                        height={height || 0}
                        width={width  || 0}
                        columnCount={columnCount}
                        columnWidth={COLUMN_WIDTH+GUTTER_SIZE}
                        rowCount={rowCount}
                        rowHeight={ROW_HEIGHT+GUTTER_SIZE}
                    >
                        {(props: CellProps) => Cell({...props, columnCount})}
                    </FixedSizeGrid>
                )
            }}
            </AutoSizer>
        </div>
    );
}