For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/infinite-scroll.md.
  • English
  • InfiniteScroll

    Load more content when reaching the end of a list.

    Basic usage

    Item 1
    Item 2
    Item 3
    Item 4
    Item 5
    Item 6
    Item 7
    Item 8
    Item 9
    Item 10
    basic.tsx
    import { InfiniteScroll, ScrollArea } from '@ifhover/leaf-ui';
    
    import { useRef, useState } from 'react';
    
    export function InfiniteScrollBasic() {
      const root = useRef<HTMLDivElement>(null);
      const [items, setItems] = useState(() => Array.from({ length: 10 }, (_, i) => i));
      return (
        <ScrollArea ref={root} height={240} aria-label="Growing list">
          <InfiniteScroll
            dataLength={items.length}
            hasMore={items.length < 40}
            target={() => root.current}
            onLoadMore={async (signal) => {
              await new Promise((resolve) => setTimeout(resolve, 500));
              if (!signal.aborted)
                setItems((previous) => [
                  ...previous,
                  ...Array.from({ length: 10 }, (_, index) => previous.length + index),
                ]);
            }}
          >
            {items.map((item) => (
              <div key={item} style={{ padding: '12px 16px' }}>
                Item {item + 1}
              </div>
            ))}
          </InfiniteScroll>
        </ScrollArea>
      );
    }
    

    More examples

    Record 1

    Record 2

    Record 3

    Record 4

    Record 5

    Record 6

    advanced.tsx
    import { InfiniteScroll, ScrollArea } from '@ifhover/leaf-ui';
    
    import { useRef, useState } from 'react';
    
    export function InfiniteScrollRetry() {
      const root = useRef<HTMLDivElement>(null);
      const attempt = useRef(0);
      const [items, setItems] = useState([0, 1, 2, 3, 4, 5]);
      return (
        <ScrollArea ref={root} height={180} aria-label="Retry example">
          <InfiniteScroll
            dataLength={items.length}
            hasMore={items.length < 12}
            target={() => root.current}
            onLoadMore={async (signal) => {
              await new Promise((resolve) => setTimeout(resolve, 300));
              if (signal.aborted) return;
              if (attempt.current++ === 0) throw new Error('Retry demo');
              setItems((previous) => [...previous, ...[6, 7, 8, 9, 10, 11]]);
            }}
          >
            {items.map((item) => (
              <p key={item}>Record {item + 1}</p>
            ))}
          </InfiniteScroll>
        </ScrollArea>
      );
    }
    

    Usage

    Return a Promise from onLoadMore; requests do not overlap. Update dataLength after appending. The signal aborts on unmount and failures can be retried.

    Types

    Import these types for data, callbacks and refs.

    import type { InfiniteScrollProps } from '@ifhover/leaf-ui';

    InfiniteScrollProps API

    PropertyTypeDefaultDescription
    dataLengthnumberRequiredLoaded count; changes allow the next request
    hasMorebooleanRequiredMore data remains
    onLoadMore(signal: AbortSignal) => Promise<void>RequiredLoad more; signal aborts the request
    target() => HTMLElement | null—Scroll target or link target
    thresholdnumber200Pixels from the end that trigger loading
    loaderReactNode—Loading placeholder
    endMessageReactNode—Content after all items are loaded
    onError(error: unknown) => void—Error callback