Repository navigation
The table dose not support lazy render on columns (horizontal level) #36
Description
Activity
- changed the title
[-]The table dose not support lazy render on columns[/-][+]The table dose not support lazy render on columns (horizontal level)[/+]on May 14, 2019 That is because here
var expandIcon = expandIconRenderer({ rowData: rowData, rowIndex: rowIndex, depth: depth, onExpand: this._handleExpand }); var cells = columns.map(function (column, columnIndex) { return cellRenderer({ isScrolling: isScrolling, columns: columns, column: column, columnIndex: columnIndex, rowData: rowData, rowIndex: rowIndex, expandIcon: column.key === expandColumnKey && expandIcon }); }); if (rowRenderer) { cells = renderElement(rowRenderer, { isScrolling: isScrolling, cells: cells, columns: columns, rowData: rowData, rowIndex: rowIndex, depth: depth }); }when render a row, we render the all columns
can we improve this?In that case I think you should consider to use a Grid instead of Table, but I think that's a interesting feature we could add in the future
which Grid? the Grid in
react-windowing?So I can not use
react-base-table, I need to implement such as fixed myself, sador you could send a pr to add virtualization support for columns in
BaseTableok, I will try
Hey, @dingchaoyan1983!
Did you have a chance to take a look columns virtualization?Reacted by Vasileios Paraskevas and Ismail Moghul+1.
I ported from a 20+ column react-virtualized multigrid (with plenty of custom renderers) to react-base-table, assuming the use of react-window would include column virtualisation as well as row virtualisation. I discover my assumption to be wrong! I now have a noticeable performance drop in scrolling. No complaint here - my fault, I should have done more research - RBT adds some amazing capability on top of react-window, but to have this would make it complete :)
Reacted by Richard Guercione of my thoughts is that if we have too many columns, it's a Grid not Table, there is no concept of Row actually, only Cell
In my context, I'd still call it table. Each row relates to a singular entity, each column is an attribute of that entity. Fixed headers/columns/sortable (etc) all conceptually make sense. But arguably, perhaps 20+ columns is too many?
Internally we have table with more then 30 columns, but only few custom columns
@nihgwu Within the TableRow render function, is there a way to find the offset at which the row is currently horizontally scrolled to? I'm messing around with a crude column virtualisation that might help with my specifics (by the way, my table is 10,000 rows, 23 columns and performance is sorta ok with Chrome but really bogs down with Firefox on macOS).
@sgilligan you can get offset via
onScrollReacted by Simon Gilligan@nihgwu this is an experiment with trying to lazy render columns. The simple approach is to not render non-visible columns when scrolling vertically, but show them all when scrolling horizontally. In my own context, it really helps with vertical scrolling performance on firefox/macos, but on first horizontal movement the columns remain un-rendered until the scroll finishes. Is this a viable approach?
the reason for the blank cells when scroll horizontally is that BaseTable optimized to avoid unnecessary re-renders, there is no internal state changed(more specific, either
dataorcolumnsshould be changed to trigger re-renderhere is a workable demo modified from yours, some notes:
- change
childrendirectly takes no effect - you can calculate the visible start and end column index and only update the rowRenderer is the two indices changed, and use those two indices instead of
scrollLeftto trigger re-render - you can merge the invisible cells into one, perhaps that would improve the performance a bit, the example may help
Reacted by Simon Gilligan, Gregory Potdevin and DUONG DUC ANH- change
aha! thanks so much @nihgwu, that gives me plenty to work with. This is much appreciated and thanks for your outstanding engagement.
@sgilligan I updated the demo to address note 3
Reacted by Gregory PotdevinReacted by Glendon Thompson@nihgwu ahh .. yes I understand now. That's a clear improvement - will use that - thanks again
- addedquestionWould be helpful to the othersWould be helpful to the othersand removedenhancementNew feature or requestNew feature or request
on Aug 10, 2020 here is a workable demo
- change
childrendirectly takes no effect - you can calculate the visible start and end column index and only update the rowRenderer is the two indices changed, and use those two indices instead of
scrollLeftto trigger re-render - you can merge the invisible cells into one, perhaps that would improve the performance a bit, the example may help
Thanks, we implemented this for our backoffice (some tables have 10k lines and 50+ columns) and the table feels much more responsive now.
Maybe this code sample could be added to the standard examples on the website ?
- change
I was able to address this in our component by adjusting the list of columns based on the viewport width and the value of
scrollLeft. Since we know the widths of all our columns, it was pretty easy to calculate those that are visible, plus one page (viewport width) on either side. Then I added filler elements to the left and right to make the scrolling look and work right.For anyone who looking for solution now because the lib have changed a lot
Here's my example code using custom hook & typescript
this code writing on version 1.13.4 and base on nihgwu's work aroundtypescript issue : #407
/** * ? This hook is used to support virtualization on horizontal scrolling because react-base-table does not support it out of the box. * * ? The idea is to nullify the cells that are not visible in the viewport. This is done by using the `rowRenderer` prop of react-base-table. */ export const useHorizontalVirtualList = ({ initialScrollLeft = 0, tableWidth, }: { initialScrollLeft?: number; tableWidth: number; }) => { const [scrollLeft, setScrollLeft] = useState(initialScrollLeft); const onScroll = useCallback<CVirtualListTableOnScroll>( (args) => { if (args.scrollLeft !== scrollLeft) { setScrollLeft(args.scrollLeft); } }, [scrollLeft], ); const rowRenderer: BaseTableProps["rowRenderer"] = ({ cells, columns, rowIndex }) => { // columns type should be array , this code writing on version 1.13.4 // this check is just for satisfy ts if (Array.isArray(columns)) { const notFrozenColumns: ColumnShape[] = columns.filter((col) => !col.frozen); // minus the frozen col right to get actual visible range const frozenRightColumnsWidth: number = columns .filter((col: ColumnShape) => col.frozen === "right") .reduce((acc, col: ColumnShape) => acc + col.width, 0); const { outside } = getColumnVisibility({ offset: scrollLeft, frozenRightColumnsWidth, columns: notFrozenColumns, tableWidth, }); outside.forEach((colIdx) => { const cell = cells[colIdx]; if (isValidElement(cell)) { cells[colIdx] = cloneElement(cell, {}, null); } }); } return cells; }; return { onScroll, rowRenderer, scrollLeft, }; }; const getColumnVisibility = ({ offset, columns, tableWidth, frozenRightColumnsWidth, }: { offset: number; columns: ColumnShape[]; tableWidth: number; frozenRightColumnsWidth: number; }) => { // build the net offset for each column const netOffsets: number[] = []; let offsetSum = 0; const leftBound = offset; const rightBound = offset + tableWidth - frozenRightColumnsWidth; const outside: number[] = []; const inside: number[] = []; columns.forEach((col) => { netOffsets.push(offsetSum); offsetSum += col.width; }); // which column offsets are outside the left and right bounds? netOffsets.forEach((columnOffset, colIdx) => { const isNotVisible = columnOffset < leftBound || columnOffset > rightBound; if (isNotVisible) { outside.push(colIdx); } else { inside.push(colIdx); } }); return { outside, inside, }; };
Usage
import BaseTable, { AutoResizer } from "react-base-table"; import type { BaseTableProps, ColumnShape } from "react-base-table"; type BaseRow = { id: string; parentId?: string | null; }; export type CVirtualListTableProps = { columns: CVirtualListTableColumnShape[]; rows: BaseRow[]; initialScroll?: { x?: number; y?: number; }; } & Partial< Pick<BaseTableProps, "fixed" | "headerRenderer" | "cellRenderer" | "height" | "onScroll"> >; export type CVirtualListTableOnScroll = TypeHelpers.NonNullUndefined<BaseTableProps["onScroll"]>; export const CVirtualListTable = forwardRef<BaseTable, BaseTableProps>( function CVirtualListTableForwardedRef(props: CVirtualListTableProps, ref?: Ref<BaseTable>) { const { columns, rows, initialScroll, height, onScroll: onScrollProp, ...otherProps } = props; const tableWidth = useRef(0); const { onScroll, rowRenderer } = useHorizontalVirtualList({ initialScrollLeft: initialScroll?.x, tableWidth: tableWidth.current, }); return ( <AutoResizer height={height}> {({ width, height: autoHeight }) => { tableWidth.current = width; return ( <BaseTable width={width} columns={columns} data={rows} height={autoHeight} rowRenderer={rowRenderer} onScroll={(args) => { onScrollProp?.(args); onScroll(args); }} {...otherProps} /> ); }} </AutoResizer> ); }, );
Reacted by BroStack, David Iglesias Sánchez, xiaoxudoo and hu
assuming I have 100 columns data, the 100 columns will rendered at once. can we support this feature of lazy render in column level?