The tremendous Tanner Linsley recently released react-virtual
. react-virtual
provides "hooks for virtualizing scrollable elements in React".
I was already using the (also excellent) react-window
for this purpose. react-window
does the virtualising job and does it very well indeed However, I was both intrigued by the lure of the new shiny thing. I've also never been the biggest fan of react-window
's API. So I tried switching over from react-window
to react-virtual
as an experiment. To my delight, the experiment went so well I didn't look back!
What did I get out of the switch?
- Simpler code / nicer developer ergonomics. The API for
react-virtual
allowed me to simplify my code and lose a layer of components. - TypeScript support in the box
- Improved perceived performance. I didn't run any specific tests to quantify this, but I can say that the same functionality now feels snappier.
I tweeted my delight at this and Tanner asked if there was commit diff I could share. I couldn't as it's a private codebase, but I thought it could form the basis of a blogpost.
Nice! Do you have a commit diff we could see?
— Tanner Linsley ⚛️ (@tannerlinsley) May 10, 2020
In case you hadn't guessed, this is that blog post...
Make that change
So what does the change look like? Well first remove react-window
from your project:
yarn remove react-window @types/react-window
Add the dependency to react-virtual
:
yarn add react-virtual
Change your imports from:
import { FixedSizeList, ListChildComponentProps } from 'react-window';
to:
import { useVirtual } from 'react-virtual';
Change your component code from:
type ImportantDataListProps = {
classes: ReturnType<typeof useStyles>;
importants: ImportantData[];
};
const ImportantDataList: React.FC<ImportantDataListProps> = React.memo(props => (
<FixedSizeList
height={400}
width={'100%'}
itemSize={80}
itemCount={props.importants.length}
itemData={props}
>
{RenderRow}
</FixedSizeList>
));
type ListItemProps = {
classes: ReturnType<typeof useStyles>;
importants: ImportantData[];
};
function RenderRow(props: ListChildComponentProps) {
const { index, style } = props;
const { importants, classes } = props.data as ListItemProps;
const important = importants[index];
return (
<ListItem button style={style} key={index}>
<ImportantThing classes={classes} important={important} />
</ListItem>
);
}
Of the above you can delete the ListItemProps
type and the associate RenderRow
function. You won't need them again! There's no longer a need to pass down data to the child element and then extract it for usage; it all comes down into a single simpler component.
Replace the ImportantDataList
component with this:
const ImportantDataList: React.FC<ImportantDataListProps> = React.memo(props => {
const parentRef = React.useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtual({
size: props.importants.length,
parentRef,
estimateSize: React.useCallback(() => 80, []), // This is just a best guess
overscan: 5
});
return (
<div
ref={parentRef}
style={{
width: `100%`,
height: `500px`,
overflow: 'auto'
}}
>
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: '100%',
position: 'relative'
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => (
<div
key={virtualRow.index}
ref={virtualRow.measureRef}
className={props.classes.hoverRow}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<ImportantThing
classes={props.classes}
important={props.importants[virtualRow.index]}
/>
</div>
))}
</div>
</div>
);
});
And you are done! Thanks Tanner for this tremendous library!