react-viewport-list

๐Ÿ“œ Virtualization for lists with dynamic item size

Usage no npm install needed!

<script type="module">
  import reactViewportList from 'https://cdn.skypack.dev/react-viewport-list';
</script>

README

React ViewPort List

NPM version typescript NPM license NPM total downloads NPM monthly downloads

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

๐Ÿ“œ Virtualization for lists with dynamic item size

React Component that render only items in viewport

Features ๐Ÿ”ฅ

  • Simple API like .map()
  • Created for dynamic item height/width (if you don't know item size)
  • Works perfectly with Flexbox (unlike other libraries with pisition: absolute)
  • Supports scroll to index
  • Supports initial index
  • Supports vertical โ†• and horizontal โ†” lists๏ธ๏ธ
  • Tiny (<2kb minified+gzipped)

Try 100k list demo

Getting Started

  • Installation:

    npm install --save react-viewport-list
    
  • Basic Usage:

    import { useRef } from 'react';
    import ViewportList from 'react-viewport-list';
    
    const ItemsList = ({ items }) => {
        const ref = useRef(null);
    
        return (
            <div className="scroll-container" ref={ref}>
                <ViewportList viewportRef={ref} items={items} itemMinSize={40} margin={8}>
                    {(item) => (
                        <div key={item.id} className="item">
                            {item.title}
                        </div>
                    )}
                </ViewportList>
            </div>
        );
    };
    
    export default ItemsList;
    

Props

name type default description
viewportRef MutableRefObject required Viewport ref object
items Array [] Array of items
itemMinSize number required Item min height (or min width for 'x' axis) in px
margin number 0 Item margin bottom (or margin right for 'x' axis) in px.
You should still set margin-bottom (or margin-right for 'x' axis) in item styles
overscan number 1 Count of "overscan" items
axis 'y' / 'x' 'y' Scroll axis
'y' - vertical, 'x' - horizontal
initialIndex number -1 Initial index of item in viewport
initialAlignToTop boolean / ScrollIntoViewOptions true scrollIntoView second argument.
Used with initialIndex
initialOffset number 0 Offset after scrollIntoView.
Used with initialIndex
children (item: any, index: number) => ReactNode required Item render function.
Similar to .map() callback

Methods

  • scrollToIndex

    Params

    name type default description
    index number -1 Item index for scroll
    alignToTop boolean / ScrollIntoViewOptions true scrollIntoView second argument
    offset number 0 Offset after scrollIntoView

    Usage

    import { useRef } from 'react';
    import ViewportList from 'react-viewport-list';
    
    const ItemsList = ({ items }) => {
        const ref = useRef(null);
        const listRef = useRef(null);
    
        return (
            <div className="scroll-container" ref={ref}>
                <ViewportList ref={listRef} viewportRef={ref} items={items} itemMinSize={40} margin={8}>
                    {(item) => (
                        <div key={item.id} className="item">
                            {item.title}
                        </div>
                    )}
                </ViewportList>
                <button className="up-button" onClick={() => listRef.current.scrollToIndex(0)} />
            </div>
        );
    };
    
    export default ItemsList;
    

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 not fixed them.

.scroll-container {
    will-change: transform;
}

Limitations

  • margin

    You should use only margin-bottom (or margin-right for 'x' axis) for items, and provide it to ViewportList props. Don't use margin-top (or margin-left for 'x' axis)

    .item {
        margin-bottom: 8px;
    }
    

Advanced Usage

  • Grouping

    ViewportList render Fragment with items in viewport

    import { useRef } from 'react';
    import ViewportList from 'react-viewport-list';
    
    const ItemsList = ({ keyItems, items }) => {
        const ref = useRef(null);
    
        return (
            <div className="scroll-container" ref={ref}>
                <span className="group-title">{'Key Items'}</span>
                <ViewportList viewportRef={ref} items={keyItems} itemMinSize={60} margin={8}>
                    {(item) => (
                        <div key={item.id} className="key-item">
                            {item.title}
                        </div>
                    )}
                </ViewportList>
                <span className="group-title">{'Items'}</span>
                <ViewportList viewportRef={ref} items={items} itemMinSize={40} margin={8}>
                    {(item) => (
                        <div key={item.id} className="item">
                            {item.title}
                        </div>
                    )}
                </ViewportList>
            </div>
        );
    };
    export default ItemsList;
    
  • Sorting

    You can use React Sortable HOC

    import { useRef } from 'react';
    import { SortableContainer, SortableElement } from 'react-sortable-hoc';
    import ViewportList from 'react-viewport-list';
    
    const SortableList = SortableContainer(({ innerRef, ...rest }) => <div {...rest} ref={innerRef} />);
    
    const SortableItem = SortableElement((props) => <div {...props} />);
    
    const ItemsList = ({ items, onSortEnd }) => {
        const ref = useRef(null);
    
        return (
            <SortableList innerRef={ref} className="scroll-container" onSortEnd={onSortEnd}>
                <ViewportList viewportRef={ref} items={items} itemMinSize={40} margin={8}>
                    {(item, index) => (
                        <SortableItem key={index} index={index} className="item">
                            {item.title}
                        </SortableItem>
                    )}
                </ViewportList>
            </SortableList>
        );
    };
    
    export default ItemsList;