A React component that can virtualise lists and any set of children

virtual-window

A list virtualiser that can create virtual rows out of arrays or a total count, or virtualise an arbitrary set of React components.

Installation

npm i virtual-window

Usage

You can place a Virtual Window over a set of arbitrary React components by simply wrapping them

function MyComponent({ list }) {
  return (
    <VirtualWindow>
      <MyComponent1 />
      {list.map(l => (
        <SomeComponent key={l.id} data={l} />
      ))}
      <MyLastComponent />
    </VirtualWindow>
  )
}

You can also use the mode where it is a virtual list, in this mode you specify a number of parameters

Parameter Default Purpose
className A class to apply to the outer wrapper component
item <Simple/> a fragment like component The item that will be used to display this element. This component is passed properties for the item being displayed and the index of it.

e.g. <VirtualWindow list={items} item={<MyComponent withAny={prop}/>} />
itemSize 36 The default size to expect for items
keyFn WeakMap of items to unique integers A function to create a key for an item in the list.

e.g. <VirtualWindow list={items} keyFn={v=>v.id}/>
list The array of items to display
pass item a string containing the name of the property to pass to the item being rendered.
onConfigure A callback function that receives properties of the Virtual Window in attributes called expectedSize and scrollingElement. The callback is triggered whenever measurement detects a change in the expected size of items.
onVisibleChanged A callback function that receives the first and last visible items as they change onVisibleChanged={(first, last)=>console.log(first, last)}.

You can use this property to update the list and provide endless scrolling.
overscan 2 The number of additional pages to render below and above the visible list for sizing
totalCount The number of records to render, this is used instead of a list to have the component totally virtual. In this case the item passed to the rendered component is the index to use for the data.

Sizing

By default the virtual item container has a height of 100% and a flex of 1. This allows it to resize into
various useful containers. If you need to specify a height then either size the wrapping component or pass a className
to the <VirtualWindow/>

Example

import { VirtualWindow } from "lib/VirtualWindow"
import { Box, IconButton } from "@material-ui/core"
import { useState, useMemo } from "react"
import { MdExpandLess, MdExpandMore } from "react-icons/md"
import randomColor from "randomcolor"
import { routes } from "./routes"
import { makeStyles } from "@material-ui/core"

const useStyles = makeStyles({
  virtualBox: {
    height: 370,
    background: "#0002",
    overflow: "auto"
  }
})


export const items = Array.from({ length: 2000 }, (_, i) => ({
  content: i,
  color: randomColor()
}))

export default function App() {
  const classes = useStyles()

  return (
    <div className="App">
      <div className={classes.virtualBox}>
        <VirtualWindow list={items} item={<DummyItem />} />
      </div>
    </div>
  )
}


export function DummyItem({ item, index }) {
  const [extra, setExtra] = useState(item.upsized || 0)
  item.upsized = extra
  const style = useMemo(
    () => ({
      minHeight: 34 + (index & 7) * 9 + extra,
      width: "100%",
      background: item.color
    }),
    [item.color, extra, index]
  )
  return (
    <Box display="flex" flexDirection="row" p={2} style={style}>
      <Box flex={1} />
      <Box
        borderRadius={4}
        bgcolor="#ffffffdc"
        color="#444"
        boxShadow="inset 0 0 6px 0px #000c"
        p={1}
        display="flex"
        alignItems="center"
      >
        <Box mr={2}>{item.content}</Box>
        <Box>{JSON.stringify(style, null, 2)}</Box>
        <Box
          ml={1}
          onClick={() => {
            if (extra) {
              setExtra(0)
            } else {
              setExtra(Math.floor(Math.random() * 90) + 20)
            }
          }}
        >
          <IconButton color="primary">
            {extra ? <MdExpandLess /> : <MdExpandMore />}
          </IconButton>
        </Box>
      </Box>
      <Box flex={1} />
    </Box>
  )
}

Demonstration

On CodeSandBox