Skip to main content

From react-window to react-virtual

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!