@virtualform/grid
v0.1.0
Published
![Virtualform hero](https://i.imgur.com/OAgybA4.png)
Downloads
17
Maintainers
Readme
The documentation for @virtualform/grid
Home · Quick Start · API · Recipes · Demo · Playground
🏁 Quick Start
As usual, you have to install it first.
yarn add @virtualform/grid
Then have fun!
import { useGrid } from '@virtualform/grid'
const App = () => {
const { getRootProps, getWrapperProps, cells } = useGrid({
cells: {
amount: 1000,
width: 100,
height: 100,
},
})
const { style, ...rootProps } = getRootProps()
return (
<div style={{ ...style, width: '100vw', height: 600 }} {...rootProps}>
<div {...getWrapperProps()}>
{cells.map((cell) => {
return (
<div {...cell.getProps()}>
<div>I am cell {cell.index}</div>
</div>
)
})}
</div>
</div>
)
}
A few quick good-to-knows:
- The code above will give you a grid of
1000
cells, - Each cell will have the min width of
100px
and the max width of100px
, - Each cell will have the fixed height of
100px
, - You must be explicit in the
height
of the root div (the one spreading...rootProps
).
:gear: API
Inputs
Below we are going to see what useGrid()
must and can receive.
cells
· Required
useGrid({
cells: {
amount: 1000,
width: 100,
height: 100,
},
})
amount
:number
· Required The absolute amount of cells to render in the grid. A cell is a single element. A gallery displaying 5000 pictures has acells.amount
of5000
.width
:number
· Required Thewidth
of the cells. To be completely honest with you, this is an estimation rather than an exact size. The reason for an estimation is because of the responsive nature of Virtualform: depending on the screen size, respecting the width passed through this variable, mathematically speaking, is not possible. So in order to achieve pixel perfection, Virtualform will take thewidth
only as a reference of what you want your cells to look like, and will do its best to be as close to it as possible.height
:number
· Required Theheight
of the cells. This is an exact size and won't ever change unless you manually do so. Just in case: the height of the cells are the height of the rows as well. Meaning, whatever the value you set asheight
, expect that to be the height of your rows.
gap
: number
, Optional
Represents the distance, in pixels, between each cell, vertically and horizontally.
useGrid({
gap: 10,
})
⚠️ Currently you cannot specify gaps at specific diretions. I.e. gap only at the top, or only at the bottom, etc.
⚠️ Gaps won't space the surroundings of your grid. Use
gutter
for that.
gutter
: number
, Optional
Represents the space, in pixels, around your virtualized grid.
useGrid({
gutter: 50,
})
⚠️ Currently you cannot specify gutter at specific diretions. I.e. gutter only at the top, or only at the bottom, etc.
overscan
: number
, Optional
Represents how many rows not visible at the viewport will be mounted.
useGrid({
overscan: 2,
})
This property is useful when you want a smoother experience when the user is scrolling around. In other words, Virtualform will mount not only the cells the user is seeing, but also the cells at the rows specified at overscan
above and below the topmost and the bottommost visible rows.
See the examples below:
The image above demonstrates what happens when your overscan
is at 0
(the default value). As you can see, only the visible cells are mounted on the DOM and this is the fastest way to use Virtualform.
Now, let's look at what happens when you have an overscan
of 2
:
As you can see, the two rows above and below the visible row will be mounted as well, meaning that when the user scrolls through these overscanned rows, they won't see that blink of components mounting.
To recap:
- A "visible" cell is the concept of a mounted cell, except it's present at the visible portion of your grid, so it's expected the user is seeing it.
- An "overscanned" cell is a mounted cell that's not visible at the viewport.
- A mounted cell is present at the DOM and will cost to the CPU to compute it, even though it's not necessarily explictly visible to the human eye.
- Not mounted cells don't cost CPU computation and only exist in memory. These are the virtualized cells.
A note of caution
Although overscan is a good technique to make the user experience even better, it's important you don't overabuse it (pun intended) because it may cause the contrary effect: making the experience worse.
- Overscan is impercetible when scrolling too fast. In this case, because of how fast the scroll occurs, it doesn't give enough time for Virtualform to compute the overscanned rows. If you expect your users to scroll very fast, you can completely skip the
overscan
property. - Overscan occurs at row-level, not cell-level. Meaning that it'll mount all cells in the
x
rows above and below the topmost and bottommost visible rows, wherex
is the number you passed tooverscan
. - Too big numbers at
overscan
will make your grid slower. For example, if you pass tooverscan
the same amount ofcells.amount
, is as if nothing is virtualized, thus Virtualform is completely needless.
My recommendation for overscan
is: while developing, start with 0
and play with your grid. If it doesn't feel completely good yet, increase it by 1
. Play with your grid again. Still think it can be smoother? Then repeat the process until you find the sweet spot. Also, a quick tip: the less columns (yes, columns, not cells), the greater the number for overscan
can safely be.
Outputs
Below we are going to see what useGrid()
returns.
getRootProps
A function that returns the necessary props the root <div />
must have in order to proper virtualize things. It's nothing scary, really, just some styles and a ref
.
const { getRootProps, ...etc } = useGrid({
// ...
})
const { style, ...rootProps } = getRootProps()
return <div style={{ ...style, height: '100vh' }} {...rootProps} />
⚠️ The root
<div />
necessarily needs an explicitheight
.
getWrapperProps
A function that returns the necessary props a <div />
inside the root div needs to properly virtualize things. styles
only, and we recommend that you don't tweak position-related styles too much otherwise your virtualization may break.
const { getRootProps, getWrapperProps } = useGrid({
// ...
})
const { style, ...rootProps } = getRootProps()
return (
<div style={{ ...style, height: '100vh' }} {...rootProps}>
<div {...getWrapperProps()} />
</div>
)
cells
An array containing the mounted cells (the visible ones and the ones used by overscan). You must render these cells inside the wrapper div.
const { getRootProps, getWrapperProps, cells } = useGrid({
// ...
})
const { style, ...rootProps } = getRootProps()
return (
<div style={{ ...style, height: '100vh' }} {...rootProps}>
<div {...getWrapperProps()}>
{cells.map((cell) => (
<div {...cell.getProps()}>Item {cell.index}</div>
))}
</div>
</div>
)
Let's take a look at the properties of a single cell, shall we?
index
:number
Use this index to get data from your actual array of data you want to display. For example, if you have an arraypictures
and you want to display your pictures in order, you have to do something likepictures[cell.index].url
.getProps
:function
A function that returns only the necessary props to use in the firstmost div of a cell. Returns akey
so you don't have to worry about it, as well as astyle
.
recompute
The function that does the virtualization magic. The same one used internally by Virtualform, exposed to you.
const { getRootProps, getWrapperProps, cells, recompute } = useGrid({
// ...
})
const { style, ...rootProps } = getRootProps()
useEffect(() => {
recompute()
}, [somethingThatMayAffectTheGrid])
return (
<div style={{ ...style, height: '100vh' }} {...rootProps}>
<div {...getWrapperProps()}>
{cells.map((cell) => (
<div {...cell.getProps()}>Item {cell.index}</div>
))}
</div>
</div>
)
I won't call it unlikely, but maybe you don't need to call recompute
, ever. Whenever an input property change, Virtualform will recompute your grid in a very optimized way. This is only exposed because under certain circumstances, you may benefit from it. That said, use wisely.
⚠️ This is an expensive task. Using it with caution it's okay and won't damage the experience, but overabusing it can freeze your app.
mountedRowsIndices
Returns an array containing the indices of all the mounted rows (number[]
).
const { getRootProps, getWrapperProps, cells, mountedRowsIndices } = useGrid({
// ...
})
const { style, ...rootProps } = getRootProps()
useEffect(() => {
alert(`There are ${mountedRowsIndices.length} in the DOM!`)
}, [mountedRows.length])
return (
<div style={{ ...style, height: '100vh' }} {...rootProps}>
<div {...getWrapperProps()}>
{cells.map((cell) => (
<div {...cell.getProps()}>Item {cell.index}</div>
))}
</div>
</div>
)
It's very important that you don't trust the order of the indices. So:
❌ Don't:
if (mountedRowsIndices[10]) {
// do something when row 10 is mounted
}
✅ Do:
if (mountedRowsIndices.includes(10)) {
// do something when row 10 is mounted
}
PRO TIP: It's very likely that you don't need this property at all. However, it's useful if you are into infinite loading.
rowsAmount
Returns the total amount (number
) of rows computed by the grid, regardless whether they are mounted or not.
colsPerRow
Returns the computed amount (number
) of columns/cells per row.
colWidth
Returns the computed width (number
), in pixels, of the columns/cells. All columns/cells have the same width.
👩🍳 Recipes
Brought to you by 🇧🇷 Guilherme "chiefGui" Oderdenge and Starchive.
The MIT License (MIT)
Copyright (c) 2022 Guilherme Oderdenge
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.