I'm trying to used react-virtualized to render a table with 1000+ rows of data. The rows are very heavy containing multiple plex React ponents. input, bobox, date selector and popup menus all in one single row. I need the entire window to scroll these rows.
I also need to group the rows and nest them into a show/hide style accordion ponent.
[+] Row Header 1
row 1
row 2
...
row 1001
[+] Row Header 2
row 1
row 2
...
row 1001
I'm unsure how to handle this use case or if React-Virtualized can handle this type of thing.
What I've tried:
Use WindowScroller/AutoSizer/List ponents in conjunction and place this group of react-virtualized ponents into each of the accordions. This works but does not solve my perf. issues because it's still too much for the browser to handle (first load is around 25 seconds and scrolling isn't usable)
Do I also need to use WindowScroller/AutoSizer/List ponents to handle the first level of Row Headers as well?
Any ideas or examples would be much appreciated.
I'm trying to used react-virtualized to render a table with 1000+ rows of data. The rows are very heavy containing multiple plex React ponents. input, bobox, date selector and popup menus all in one single row. I need the entire window to scroll these rows.
I also need to group the rows and nest them into a show/hide style accordion ponent.
[+] Row Header 1
row 1
row 2
...
row 1001
[+] Row Header 2
row 1
row 2
...
row 1001
I'm unsure how to handle this use case or if React-Virtualized can handle this type of thing.
What I've tried:
Use WindowScroller/AutoSizer/List ponents in conjunction and place this group of react-virtualized ponents into each of the accordions. This works but does not solve my perf. issues because it's still too much for the browser to handle (first load is around 25 seconds and scrolling isn't usable)
Do I also need to use WindowScroller/AutoSizer/List ponents to handle the first level of Row Headers as well?
Any ideas or examples would be much appreciated.
Share Improve this question asked Sep 13, 2018 at 4:11 jerpsu15jerpsu15 3411 gold badge4 silver badges5 bronze badges 3- On the same issue right now. Have you find a nice solution to share? Currently thinking about two possible options: 1) using virtualized Grids on each level (Grid for groups and a grid for subItems when the row is expanded) 2) use only one virtualized grid, prepare my data to tree-like structure and just change rowCount when expanding items – Mike Yermolayev Commented Nov 27, 2019 at 12:20
- Do you need to display all of those ponents, or you only have a header that will later be expanded showing the plex ponent structure? – Ivan Satsiuk Commented Jan 14, 2021 at 18:20
- I wrote a response to a similar question here. I hope this helps you. – yatsemirsky Commented Sep 24, 2023 at 8:16
2 Answers
Reset to default 1You can at least free up the UI thread for scrolling (of course an important UX principle) with web workers.
Here is a medium-length discussion article with an example, a quick implementation doc (and the great matching article), and my all-time favorite talk on the subject.
This defers the effort from the main "UI" thread, but you can also prevent this deferment in the first place if the effort can be memoized with the useMemo() hook.
The critical piece of react-virtualized is reputeRowHeights
you can achieve the desired results with this.
import React, { useState, useRef, useEffect } from "react";
import {
AutoSizer,
Column,
Table,
defaultTableRowRenderer
} from "react-virtualized";
const Component = ({ list }) => {
const [selectedIndex, setSelectedIndex] = useState(-1);
const tableRef = useRef();
const Details = ({ children, index }) => (
<div style={{ cursor: "pointer" }} onClick={() => setSelectedIndex(index)}>
{children}
</div>
);
const _getDatum = index => list[index % list.length];
const _getRowHeight = ({ index }) => (index === selectedIndex ? 96 : 48);
const rowGetter = ({ index }) => _getDatum(index);
const cellRenderer = ({ rowIndex }) => {
if (rowIndex !== selectedIndex) {
return <Details index={rowIndex}>+</Details>;
} else {
return <Details index={-1}>-</Details>;
}
};
useEffect(
() => {
tableRef.current.reputeRowHeights();
},
[selectedIndex]
);
const rowRenderer = props => {
const { index, style, className, key, rowData } = props;
if (index === selectedIndex) {
return (
<div
style={{ ...style, display: "flex", flexDirection: "column" }}
className={className}
key={key}
>
{defaultTableRowRenderer({
...props,
style: { width: style.width, height: 48 }
})}
<div
style={{
marginRight: "auto",
marginLeft: 80,
height: 48,
display: "flex",
alignItems: "center"
}}
>
{rowData.details}
</div>
</div>
);
}
return defaultTableRowRenderer(props);
};
return (
<div style={{ height: "90vh" }}>
<AutoSizer>
{({ width, height }) => (
<Table
ref="Table"
headerHeight={56}
height={height}
overscanRowCount={10}
rowHeight={_getRowHeight}
rowGetter={rowGetter}
rowCount={1000}
width={width}
ref={tableRef}
rowRenderer={rowRenderer}
>
<Column
label="Index"
cellDataGetter={({ rowData }) => rowData.length}
cellRenderer={cellRenderer}
dataKey="index"
disableSort
width={60}
/>
<Column dataKey="name" disableSort label="Full Name" width={120} />
</Table>
)}
</AutoSizer>
</div>
);
};
export default Component;
Here is the codesandbox