Reputation: 341
I'm trying to used react-virtualized to render a table with 1000+ rows of data. The rows are very heavy containing multiple complex React components. input, combobox, 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 component.
[+] 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 components in conjunction and place this group of react-virtualized components 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 components to handle the first level of Row Headers as well?
Any ideas or examples would be much appreciated.
Upvotes: 17
Views: 4870
Reputation: 2053
The critical piece of react-virtualized is recomputeRowHeights
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.recomputeRowHeights();
},
[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
Upvotes: 0
Reputation: 1183
You 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.
Upvotes: 1