Introduction
What is a virtualized window?​
@resembli/react-virtualized-window
provides components that render only what is visible to the user into the DOM.
This keeps your website responsive and performant. The Grid
below has 100,000 cells, but only the cells visible to the user
are actually present in the DOM.
import type { RenderItem } from "@resembli/react-virtualized-window"
import { Grid } from "@resembli/react-virtualized-window"
const data = Array.from({ length: 1000 }, (_, row) => {
return Array.from({ length: 100 }, (_, column) => {
return [row, column]
})
})
const GridCell: RenderItem<number[]> = ({
data,
style,
cellMeta: { row, column, pinnedRow, pinnedColumn },
}) => {
let background = (row + column) % 2 === 1 ? "#f8f8f0" : "white"
if (pinnedRow === "top") background = "#839073"
if (pinnedColumn === "left") background = "#4E4A59"
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
background,
color: pinnedColumn || pinnedRow ? "rgb(30, 255, 0)" : "black",
...style,
}}
>
{data[0]},{data[1]}
</div>
)
}
export function BasicGridExample() {
return (
<Grid
height={600}
defaultRowHeight={60}
defaultColumnWidth={60}
pinnedTopCount={1}
pinnedLeftCount={1}
data={data}
>
{GridCell}
</Grid>
)
}
What does @resembli/react-virtualized-window
support?​
Some of the common features supported are:
- Row virtualization
- Column virtualization
- Row and column pinning
- React 18 concurrent mode ready
- TypeScript typings
- Variable width and height data items
- Automatic window sizing out the box, with resize handling
- SSR rendering support
- and much more...
Installation​
- npm
- pnpm
- yarn
npm install @resembli/react-virtualized-window
pnpm i @resembli/react-virtualized-window
yarn add @resembli/react-virtualized-window
Brief overview of the react-virtualized-window components​
Component | Description |
---|---|
List | Useful for rendering a list of items. The List component vertically or horizontally virtualizes the view |
Grid | Useful for rendering tabular data. The Grid component horizontally and vertically virtualizes the view |
Basic walkthrough​
Step 1: Creating a sized container​
The virtualized components all work the same way, and share the same base implementation. The first thing you need to do is create a sized container.
A sized container must have a width and height even when it is empty. It should also not grow as the contents rendered inside it grows. The simplested
and most straight forward way is to create a div
with some height and width, e.g:
import { Grid } from "@resembli/react-virtualized-window"
function MyGrid() {
return <div style={{ height: 400, width: 400 }}>{/* To be filled in soon */}</div>
}
info
We are using the <Grid/>
component here which virtualizes horizontally and vertically and hence requires both width and height to be sized.
In the case of the <List/>
only the height or width is required to be sized dependent on the Layout
property (which can either be vertical
or horizontal
).
tip
You can use percentage sizing as well but you need to ensure the parent itself resolves to a size. For example, the following is a valid sized container setup for the window components:
<div style={{ display: "grid", gridTemplateColumns: "400px, 800px", gridTemplateRows: 400 }}>
<Grid width="50%" height="50%" />
</div>
In the above sizing container, the <Grid/>
window is rendered in the first column of the first row. The first column is 400px
wide and 400px
tall. We specify the <Grid/>
should take up 50%
of the width and height, hence the <Grid/>
component will be 200px
wide
and tall.
Step 2: Creating a render component​
Next we need to create a React component that we can use to render our individual items. The window components will use the component to render
the items. For each data item, the window component will pass the data for the that item, the styles we need to apply to the item, and some meta data
about the item's position in the grid as props
to the component. The metadata is not necessary for rendering items. We now have the following:
import { Grid } from "@resembli/react-virtualized-window"
function RenderItem(props) {
const [row, column] = props.data // The piece of data for this item.
const style = props.style // The CSSProperties we need to pass to our item
return (
<div
style={{
border: "1px solid gray",
boxSizing: "border-box",
display: "flex",
justifyContent: "center",
alignItems: "center",
...style,
}}
>
{row},{column}
</div>
)
}
function MyGrid() {
return <div style={{ height: 400, width: 400 }}>{/* To be filled in soon */}</div>
}
Notice this just a regular React component. It's important that we apply the style passed as a prop to our component.
caution
The style
prop passed to our RenderItem
component contains styles relevant to the sizing of the component. In particular
it sets the width
, height
, and margin
CSS values.
It's important these dimensions remain unchanged and items need to have known widths and heights for the virtualization to work correctly.
A common mistake is to apply a border to a RenderItem
, e.g.
<div style={{ border: "1px solid black", ...style }}>...</div>
However this changes the dimension of the div
if the box-sizing
style is content-box
, which is the default in browsers. The correct way to
give a virtualized item a border to set the box-sizing
style to border-box
, in addition to setting the border value:
<div style={{ border: "1px solid black", boxSizing: "border-box", ...style }}>...</div>
Step 3: Render the window component with our data​
We can now render the componet by passing our data to the grid. It's important we pass our RenderItem
as a value, and not as a JSX
object.
We are rendering the <Grid>
component here so the data we pass should be a matrix (i.e. an array of arrays).
import { Grid } from "@resembli/react-virtualized-window"
// Create a 1000x100 matrix
const dummyData = Array.from({ length: 1000 }, (_, row) =>
Array.from({ length: 100 }, (_, column) => [row, column]),
)
function RenderItem(props) {
const [row, column] = props.data // The piece of data for this item.
const style = props.style // The CSSProperties we need to pass to our item
return (
<div
style={{
border: "1px solid gray",
boxSizing: "border-box",
display: "flex",
justifyContent: "center",
alignItems: "center",
...style,
}}
>
{row},{column}
</div>
)
}
function MyGrid() {
return (
<div style={{ height: 400, width: 400 }}>
<Grid defaultRowHeight={50} defaultColumnWidth={100} data={dummyData}>
{RenderItem}
</Grid>
</div>
)
}
tip
It is possible (and okay) to define your RenderItem
inline, and in fact you many of our examples do this. For example:
function MyGrid() {
return (
<div style={{ height: 400, width: 400 }}>
<Grid defaultRowHeight={50} defaultColumnWidth={100} data={dummyData}>
{(props) => {
// Our render item component as before
}}
</Grid>
</div>
)
}
Note you should not use any React hooks when defining the component inline, and in fact this is one disadvantage of using
an inline function instead of a separate RenderItem
.
The end result:
See the Docs for the individual components​
Or checkout the recipes​
Basic concepts​
The NumberOrPercent
type​
The different window components accept columnWidths
and rowHeights
properties, as well as their corresponding defaults (defaultColumnWidth
and defaultRowHeight
).
The value provided to these components can either be number or a percent string. For example:
// the default row height is assumed to be 40
<List defaultRowHeight={40}>...</List>
// or the default row height is assumed to be 10% of the window height
<List defaultRowHeight="10%">...</List>
The number
value is straight forward, 40 === 40px
. However, percent values are not the same as the CSS
percent value representation.
Row height percent values are based on the window
height. Colum width percent values are based on the the window
width.
caution
If the component has a gap
value set, this will not be included as part of the percentage calcuation, so the items width or height will be the
percentage px value + gap px value
.
The sticky
div​
On most browsers scrolling happens on a separate thread. Virtualization calculations happen on the main thread. This means it's possible for the user to scroll before the window has determined what are next items to render and what position they should be in. This leads to content flashing in. This issue is present in all virtualization libaries and inherent to the way browsers work. This is execellent article for those looking to understand more.
Despite the explanation given above, you may notice that when you scroll quickly the contents within the @resembli/react-virtualized-window
components
do not flash in and out and the checkboarding pattern is not present.
This is due to a sticky
positioning div that keeps the window in place and in view even as the user scrolls. For example, if we compare
the rendering of the FixedSizedGrid
from react-window to a fixed sized grid example
from @resembli/react-virtualized-window
, we can see checkboarding vs no checkboarding in effect:
info
This is NOT a performance comparison, or even a comparison of react-window
to @resembli/react-virtualized-window
. This is a demonstration of
the checkboarding effect present in other virtualization
frameworks, which is not present in @resembli/react-virtualized-window
(unless you disable the stick div
using the disableSticky
prop).
The grids below have the following characteristics:
- Heigth and Width of
800px
- Column width of
100px
- Row height of
30px
react-window
:@resembli/react-virtualized-window
Tab-index​
It's possible to give individual items rendered into the window components a tab index - but this is not recommended. If you do give individual items a tab index
be sure to set the overscan
property to something greater than 1. This is necessary to allow the browser to tab to items that are out of view.
caution
The window components are intended to render large number of items (10,000+ in many cases). If the individual items have tab indices set, its possible the user can get stuck tabbing through all the items. For this reason we do not recommend adding a tab index to the individual items.
You should add a value to the tabIndex
property available on the window componets. This will allow the user to tab to the list and control it with the other
keyboard keys (up, down, page-up, page-down, etc).
Searching for text​
Browsers provide the ability for users to search for a string of text by pressing Ctrl-F
(or Cmd-F
on a Mac). This functionality relies on text content being
rendered to the dom. Virtualization is the process of rendering only what is visible to the dom. Hence, items that are out of view will not be searchable by the
browser. The common workaround for this issue is to build your own search logic - which is able to the scoll to the desired item. You can do this with the imperative
VirtualizedWindowApi
.