react-base-table
react-base-table copied to clipboard
The table dose not support lazy render on columns (horizontal level)
assuming I have 100 columns data, the 100 columns will rendered at once. can we support this feature of lazy render in column level?
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
?
https://react-window.now.sh/#/examples/grid/fixed-size
So I can not use react-base-table
, I need to implement such as fixed myself, sad
or you could send a pr to add virtualization support for columns in BaseTable
ok, I will try
Hey, @dingchaoyan1983! Did you have a chance to take a look columns virtualization?
+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 :)
one 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 onScroll
@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 data
or columns
should be changed to trigger re-render
here is a workable demo modified from yours, some notes:
- change
children
directly 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
scrollLeft
to trigger re-render - you can merge the invisible cells into one, perhaps that would improve the performance a bit, the example may help
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
@nihgwu ahh .. yes I understand now. That's a clear improvement - will use that - thanks again
here is a workable demo
- change
children
directly 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
scrollLeft
to 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 ?
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 around
typescript 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>
);
},
);