Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/oleggrishechkin/react-viewport-list
π Virtualization for lists with dynamic item size
https://github.com/oleggrishechkin/react-viewport-list
react react-virtual react-virtualized react-window virtualization
Last synced: 7 days ago
JSON representation
π Virtualization for lists with dynamic item size
- Host: GitHub
- URL: https://github.com/oleggrishechkin/react-viewport-list
- Owner: oleggrishechkin
- License: mit
- Created: 2019-10-22T20:06:22.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2024-06-16T10:09:32.000Z (7 months ago)
- Last Synced: 2024-12-20T04:06:44.014Z (14 days ago)
- Topics: react, react-virtual, react-virtualized, react-window, virtualization
- Language: TypeScript
- Homepage: https://codesandbox.io/s/react-viewport-list-xw2rt
- Size: 860 KB
- Stars: 231
- Watchers: 8
- Forks: 19
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# React ViewPort List
[![NPM version](https://img.shields.io/npm/v/react-viewport-list.svg?style=flat)](https://www.npmjs.com/package/react-viewport-list)
![typescript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)
![NPM license](https://img.shields.io/npm/l/react-viewport-list.svg?style=flat)
[![NPM total downloads](https://img.shields.io/npm/dt/react-viewport-list.svg?style=flat)](https://npmcharts.com/compare/react-viewport-list?minimal=true)
[![NPM monthly downloads](https://img.shields.io/npm/dm/react-viewport-list.svg?style=flat)](https://npmcharts.com/compare/react-viewport-list?minimal=true)> If your application renders long lists of data (hundreds or thousands of rows), we recommended using a technique known as βwindowingβ. This technique only renders a small subset of your rows at any given time, and can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created.
\- [React.js documentation](https://reactjs.org/docs/optimizing-performance.html#virtualize-long-lists)
## π Virtualization for lists with dynamic item size
## Features π₯
- Simple API like [**Array.Prototype.map()**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- Created for **dynamic** item `height` or `width` (if you don't know item size)
- Works perfectly with **Flexbox** (unlike other libraries with `position: absolute`)
- Supports **scroll to index**
- Supports **initial index**
- Supports **vertical** β and **horizontal** β listsοΈοΈ
- Tiny (about **2kb** minified+gzipped)Try 100k list [demo](https://codesandbox.io/s/react-viewport-list-xw2rt)
## Getting Started
- ### Installation:
```shell script
npm install --save react-viewport-list
```- ### Basic Usage:
```typescript jsx
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';const ItemList = ({
items,
}: {
items: { id: string; title: string }[];
}) => {
const ref = useRef(
null,
);return (
{(item) => (
{item.title}
)}
);
};export { ItemList };
```MutableRefObject\ / RefObject\ / { current: HTMLElement / null } / null
## Props
| name | type | default | description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `viewportRef` | MutableRefObject\ / RefObject\ / { current: HTMLElement / null } / null | required | Viewport and scroll container.
`document.documentElement` will be used if `viewportRef` not provided. |
| `items` | T[] | [] | Array of items. |
| `itemSize` | number | 0 | Item average (estimated) size (`height` for `axis="y"` and `width` for `axis="x"`) in px.
Size should be greater or equal zero.
Size will be computed automatically if `itemMinSize` not provided or equal zero. |
| `itemMargin` | number | -1 | Item margin (`margin-bottom` for `axis="y"` and `margin-right` for `axis="x"`) in px.
Margin should be greater or equal -1.
Margin will be computed automatically if `margin` not provided or equal -1.
You should still set margin in item styles |
| `overscan` | number | 1 | Count of "overscan" items. |
| `axis` | "y" / "x" | 'y' | Scroll axis:
- "y" - vertical
- "x" - horizontal
| `initialIndex` | number | -1 | Initial item index in viewport. |
| `initialAlignToTop` | boolean | true | [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) param.
Used with `initialIndex` |
| `initialOffset` | number | 0 | Offset after `scrollIntoView` call.
Used with `initialIndex`.
This value will be added to the scroll after scroll to index. |
| `initialDelay` | number | -1 | `setTimeout` delay for initial `scrollToIndex`.
Used with `initialIndex`. |
| `initialPrerender` | number | 0 | Used with `initialIndex`.
This value will modify initial start index and initial end index like `[initialIndex - initialPrerender, initialIndex + initialPrerender]`.
You can use it to avoid blank screen with only one initial item rendered |
| `children` | (item: T, index: number, array: T[]) => ReactNode | required | Item render function.
Similar to [`Array.Prototype.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). |
| `onViewportIndexesChange` | (viewportIndexes: [number, number]) => void | optional | Will be called on rendered in viewport indexes change. |
| `overflowAnchor` | "none" / "auto" | "auto" | Compatibility for `overflow-anchor: none`.
Set it to "none" if you use `overflow-anchor: none` in your parent container styles. |
| `withCache` | boolean | true | Cache rendered item heights. |
| `scrollThreshold` | number | 0 | If scroll diff more than `scrollThreshold` setting indexes was skipped. It's can be useful for better fast scroll UX. |
| `renderSpacer` | (props: { ref: MutableRefObject; style: CSSProperties; type: 'top' / 'bottom' }) => ReactNode | ({ ref, style }) => \
| `count` | number | optional | You can use items count instead of items directly. Use should use different children: (index: number) => ReactNode |
| `indexesShift ` | number | 0 | Every time you unshift (prepend items) you should increase indexesShift by prepended items count. If you shift items (remove items from top of the list you should decrease indexesShift by removed items count). |
| `getItemBoundingClientRect ` | (element: Element) => DOMRect / { bottom: number; left: number; right: number; top: number; width: number; height: number; } | (element) => element.getBoundingClientRect() | You can use custom rect getter to support `display: contents` or other cases when `element.getBoundingClientRect()` returns "bad" data |
## Methods
### scrollToIndex
scrollToIndex method has only one param - options;
**Options param**
| name | type | default | description |
| ------------ | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index` | number | -1 | Item index for scroll. |
| `alignToTop` | boolean | true | [scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) param. Only boolean option supported. |
| `offset` | number | 0 | Offset after `scrollIntoView ` call.
This value will be added to the scroll after scroll to index. |
| `delay` | number | -1 | `setTimeout` delay for initial `scrollToIndex`. |
| `prerender` | number | 0 | This value will modify initial start index and initial end index like `[index - initialPrerender, index + initialPrerender]`.
You can use it to avoid blank screen with only one initial item rendered |
**Usage**
```typescript jsx
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';
const ItemList = ({
items,
}: {
items: { id: string; title: string }[];
}) => {
const ref = useRef(null);
const listRef = useRef(null);
return (
{(item) => (
{item.title}
)}
listRef.current.scrollToIndex({
index: 0,
})
}
/>
);
};
export { ItemList };
```
### getScrollPosition
getScrollPosition returns an object with scroll position: `{ index: number, offset: number }`
**Returns**
| name | type | description |
| -------- | ------ | ----------------------------------------------------------------------------------------------------- |
| `index` | number | Item index for scroll. |
| `offset` | number | Offset after `scrollIntoView ` call.
This value will be added to the scroll after scroll to index. |
If `items=[]` or `count=0` getScrollPosition returns `{ index: -1; offset: 0 }`
**Usage**
```typescript jsx
import { useEffect, useRef } from 'react';
import { ViewportList } from 'react-viewport-list';
const ItemList = ({
items,
}: {
items: { id: string; title: string }[];
}) => {
const ref = useRef(null);
const listRef = useRef(null);
useEffect(
() => () => {
window.sessionStorage.setItem(
'lastScrollPosition',
JSON.stringify(
listRef.current.getScrollPosition(),
),
);
},
[],
);
return (
{(item) => (
{item.title}
)}
);
};
export { ItemList };
```
## Performance
If you have performance issues, you can add `will-change: transform` to a scroll container.
You should remember that in some situations `will-change: transform` can cause performance issues instead of fixing them.
```css
.scroll-container {
will-change: transform;
}
```
## Children pseudo-classes
`ViewportList` render two elements (spacers) before first rendered item and after last rendered item.
That's why children pseudo-classes like `:nth-child()`, `:last-child`, `:first-child` may work incorrectly.
## Margin
If you want more accurate virtualizing you should use equal margin for all items.
Also, you should use `margin-top` or `margin-bottom` (not both) for `axis="y"` and `margin-right` or `margin-left` (not both) for `axis="x"`.
If you want to use different margins and stil want more accurate virtualizing you can wrap your items in some element like `
## Non-keyed
You should avoid non-keyed usage of list. You should provide unique key prop for each list items.
If you have issues with scroll in Safari and other browsers without `overflow-anchor` support, check item's `key` prop.
## Advanced Usage
- ### Grouping
`ViewportList` render `Fragment` with items in viewport. So, grouping just work.
```typescript jsx
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';
const GroupedItemList = ({
keyItems,
items,
}: {
keyItems: { id: string; title: string }[];
items: { id: string; title: string }[];
}) => {
const ref = useRef(null);
return (
Key Items
{(item) => (
{item.title}
)}
Items
{(item) => (
{item.title}
)}
);
};
export { GroupedItemList };
```
- ### Sorting
You can use [React Sortable HOC](https://github.com/clauderic/react-sortable-hoc)
```javascript
import { useRef } from 'react';
import {
SortableContainer,
SortableElement,
} from 'react-sortable-hoc';
import { ViewportList } from 'react-viewport-list';
const SortableList = SortableContainer(
({ innerRef, ...rest }) => (
),
);
const SortableItem = SortableElement(
(props) =>
);
const SortableItemList = ({
items,
onSortEnd,
}) => {
const ref = useRef(null);
return (
{(item, index) => (
{item.title}
)}
);
};
export { SortableItemList };
```
- ### Scroll to position
Scroll to position may work incorrectly because scrollHeight and scrollTop (or scrollWidth and scrollLeft) changed automatically while scrolling.
But you can scroll to position with `scrollToIndex` method with `{ index: 0, offset: scrollPosition }`. For initial scroll to position you can use `initialIndex={0}` and `initialOffset={scrollPosition}`. You should remember that after scroll happened scroll position can be not equal to specified offset.
```typescript jsx
import { useRef } from 'react';
import { ViewportList } from 'react-viewport-list';
const ItemList = ({
items,
savedScroll,
}: {
items: { id: string; title: string }[];
savedScroll: number;
}) => {
const ref = useRef(null);
const listRef = useRef(null);
return (
{(item) => (
{item.title}
)}
{
// this sets scrollTop of "scroll-container" to 1000
listRef.current.scrollToIndex({
index: 0,
offset: 1000,
});
}}
/>
);
};
export { ItemList };
```
- ### Tests
You can mock ViewportList for unit tests:
```javascript
import {
useImperativeHandle,
forwardRef,
} from 'react';
export const ViewportListMock = forwardRef(
({ items = [], children }, ref) => {
useImperativeHandle(
ref,
() => ({
scrollToIndex: () => {},
}),
[],
);
return (
<>
{items.map(children)}
>
);
},
);
export default ViewportListMock;
```