JSPM

react-procedural-scroller

1.0.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 19
  • Score
    100M100P100Q67267F
  • License ISC

A headless React hook for building infinite scroller components that display procedurally generated data, perfect for use cases like date scrollers, where row data is generated dynamically rather than fetched from a dataset. Notable features include: full row virtualization for buttery-smooth performance, support for vertical and horizontal layouts, and a scrollToIndex API for instant or smooth programmatic scrolling.

Package Exports

  • react-procedural-scroller

Readme

A headless React hook for building infinite scroller components that display procedurally generated data. Perfect for use cases like date scrollers, where row data is generated dynamically rather than fetched from a dataset.

  • ⚡️Full row/column virtualization for buttery-smooth performance.
  • ⤴️ Supports vertical and horizontal layouts.
  • 📏 Supports dynamic row/column sizes.
  • 🎯 A scrollToIndex API for instant or smooth programmatic scrolling.
  • 📦 Lightweight ~ 4.95 kB when minified and gzipped.
  • 🛠 Zero dependencies.
  • 🎭 E2E tested and rigorously vetted for rock-solid reliability.

Quick Start Guide

Follow the steps below to build your first procedural scroller component:

1. Install

Install the library using npm:

npm install react-procedural-scroller

2. Import

ES Modules:

import { useProceduralScroller } from "react-procedural-scroller";

CommonJS:

const { useProceduralScroller } = require("react-procedural-scroller");

3. Build UI Components

This library is headless, meaning it only manages the scrolling state and virtualization logic, giving you full freedom to design your own UI. To get started, you’ll first need to create two components: a scrollable <Container /> element, and an <Item /> component that will be rendered inside the container to form the rows/columns. Each item is identified by an integer index, and you can procedurally generate the item's content based on this index, as shown in the date scroller examples below:

Example container:

import { useProceduralScroller } from "react-procedural-scroller";
import Item from "../components/items/item.tsx";

export default function Container() {

  const { items, container } = useProceduralScroller<
    HTMLDivElement, // Type of the scrollable container element.
    HTMLDivElement // Type of each item inside the container.
  >({
    // Initial scroll position of the container.
    initialScroll: {
      index: 0, // Index of the item to scroll to initially.
      block: "center", // Alignment of the item in the viewport: "start", "center", or "end".
    },
    /* Callback to return the minimum size of each item along the relevant axis:
      - For vertical scrolling: minimum height.
      - For horizontal scrolling: minimum width. */
    getMinItemSize: () => 100,
    // Direction of scrolling: "vertical" or "horizontal"
    scrollDirection: "vertical",
    /* Optional: `initialContainerSize` defines the height of the container on the
    first page render. Without it, `items` will be null on the first render because the hook
    needs to measure the container's size before determining how many items to render.
    Providing a value makes `items` available immediately and helps avoid layout shift. */
    initialContainerSize: 200,
  });

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column", // Must match the hook's `scrollDirection`: "column" - vertical, "row" - horizontal.
        height: "500px",
        width: "500px",
        overflow: "scroll", // The container must be scrollable!
        border: "2px solid lightgrey",
        borderRadius: "10px",
        margin: "30px",
        scrollbarWidth: 'none',
      }}
      ref={container.ref} // Enables the hook to track and update the container's scroll position.
    >
      {items?.map((item) => (
        <Item key={item.index} item={item} />
      ))}
    </div>
  );
}

Example item:

import { memo } from 'react';
import { type Item } from 'react-procedural-scroller';

export default memo(
  function Item({ item }: { item: Item<HTMLDivElement> }) {

    /* Generate a date relative to today based on the item's index
       (e.g., index 0 = today, 1 = tomorrow, -1 = yesterday). */
    const date = new Date();
    date.setDate(date.getDate() + item.index);

    return (
      <div
        ref={item.ref} // Enables the hook to measure and virtualize this item.
        style={{
          minHeight: 100, // Must match or exceed the result of getMinItemSize(index) in the container.
          borderTop: '1px solid lightgrey',
        }}
      >
        <p
          style={{
            fontFamily: 'Helvetica',
            color: 'grey',
            padding: '10px',
          }}
        >
          {date.toDateString()}
        </p>
      </div>
    );
  },
  /*
   * Since an <Item /> component’s content is generated entirely from its index,
   * it is highly recommended to wrap it in React.memo with a custom comparison
   * function that prevents re-renders unless this index changes.
   */
  (prevProps, nextProps) => prevProps.item.index === nextProps.item.index,
);

4. Implement programmatic scrolling

react-procedural-scroller includes a scrollToIndex API, allowing you to scroll to any item programmatically. For example, you can use scrollToIndex to create a “Back to today!” button for the date scroller component from Step 2:

const { items, container, scrollToIndex } = useProceduralScroller(...)
<button
  onClick={() => {
    scrollToIndex({
      index: 0,
      block: 'center',
      behavior: 'smooth',
    });
  }}
>
  Back to today!
</button>

5. You're done!

Copying the examples above should result in a date scroller component like this:

Demo GIF


API Reference

Props

Prop Required Type Default Description
initialScroll no

{ 
  block: 
    | 'start' 
    | 'center' 
    | 'end'; 
  index: number;
}

{
  block: 'center',
  index: 0 
}
Defines the initial scroll position of the container. index is the item to scroll to, and block determines alignment in the viewport.
getMinItemSize yes

(index: number) => number
- Function that must be provided to the hook for virtualization to work. It returns the minimum size of each item along the scrolling axis (height for vertical, width for horizontal). The returned value does not need to be the actual size of the item, but it must be a hard lower bound—otherwise, the hook may fail to render enough items to fully cover the container, breaking virtualization.
scrollAreaScale no number 3 The scrollable area’s size relative to the container. For example, a value of 3 makes the scrollable area at least three times the container’s size. Larger values reduce the frequency of scroll wrap events but cause more items to be rendered at once.
minIndex no number - Minimum item index that can be scrolled to.
maxIndex no number - Maximum item index that can be scrolled to.
paddingAreaScale no

{
  start: number;
  end: number; 
}

{ 
  start: 1,
  end: 1
}
Similar to scrollAreaScale but for the padding space before the first item and after the last item.
scrollDirection no
'horizontal' | 'vertical'
'vertical' Sets the scroll orientation. Must match the scrollable direction of your container.
initialContainerSize no number - Optional height of the container on first render. Makes items available immediately and helps prevent layout shift.
validateLayouts no

{
  container?: boolean;
  items?: boolean;
}

{
  container: true,
  items: true
}
Allows you to selectively disable the hook’s built-in layout validation checks. By default, the hook ensures that the container has a bounded size and that rendered items meet the minimum size returned by getMinItemSize. Setting container to false prevents the hook from throwing an error if the container size is unbounded (which can lead to infinite render loops), while setting items to false disables the check that item sizes meet the minimum required value.

Return Values

Return Value Type Description
scrollToIndex

(input: 
  {
    index: number;
    block?: 
      | 'start' 
      | 'center' 
      | 'end';
    behavior?: 
      | 'smooth' 
      | 'instant';
  }
) => void
Function to programmatically scroll to a specific item. index is the target item, block determines alignment, and behavior controls smooth or instant scrolling.
container

{
  ref: RefObject;
}
Object containing a ref to the scrollable container. Must be attached to your container element so the hook can track scroll position and virtualization.
items

{
  index: number;
  ref: RefObject;
}[] | null
Array of items to render in the container; initially null on first page load.