Skip to main content
useInfiniteList is a modified version of TanStack Query’s useInfiniteQuery for retrieving items from a resource with infinite scrolling/pagination support. It uses the getList method from the dataProvider.

Usage

import { useInfiniteList } from "@refinedev/core";

interface IProduct {
  id: number;
  name: string;
  price: number;
}

const productList = useInfiniteList<IProduct>({
  resource: "products",
  pagination: {
    current: 1,
    pageSize: 10,
  },
});

const { data, hasNextPage, fetchNextPage } = productList.query;

Parameters

resource
string
required
Resource name for API data interactions.
pagination
Pagination
Pagination configuration for the query.
sorters
CrudSort[]
Sorter parameters for ordering results.
filters
CrudFilter[]
Filter parameters for narrowing down results.
meta
MetaQuery
Meta data for the dataProvider. Can be used to pass additional parameters to data provider methods.
dataProviderName
string
If there is more than one dataProvider, you should specify which one to use.
queryOptions
UseInfiniteQueryOptions
TanStack Query’s useInfiniteQuery options.
successNotification
OpenNotificationParams | false | ((data, params) => OpenNotificationParams | false)
Success notification configuration. Set to false to disable.
errorNotification
OpenNotificationParams | false | ((error, params) => OpenNotificationParams | false)
Error notification configuration. Set to false to disable.
liveMode
'auto' | 'manual' | 'off'
Live/Realtime mode configuration.
onLiveEvent
(event) => void
Callback to handle live events.
liveParams
object
Additional parameters for live queries.
onSuccess
(data: InfiniteData<GetListResponse<TData>>) => void
Callback function called on successful query.
onError
(error: TError) => void
Callback function called on failed query.
overtimeOptions
UseLoadingOvertimeOptionsProps
Configuration for loading overtime behavior.

Return Values

result
object
Simplified result object.
query
UseInfiniteQueryResult
TanStack Query’s useInfiniteQuery return object.
overtime
object
Loading overtime information.

Examples

Basic Infinite Scroll

import { useInfiniteList } from "@refinedev/core";

const { query } = useInfiniteList({
  resource: "posts",
  pagination: {
    pageSize: 10,
  },
});

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = query;

// Flatten all pages into a single array
const allPosts = data?.pages.flatMap((page) => page.data) ?? [];

return (
  <div>
    {allPosts.map((post) => (
      <div key={post.id}>{post.title}</div>
    ))}
    
    {hasNextPage && (
      <button
        onClick={() => fetchNextPage()}
        disabled={isFetchingNextPage}
      >
        {isFetchingNextPage ? "Loading..." : "Load More"}
      </button>
    )}
  </div>
);

Infinite Scroll with Intersection Observer

import { useInfiniteList } from "@refinedev/core";
import { useEffect, useRef } from "react";

const ProductList = () => {
  const { query } = useInfiniteList({
    resource: "products",
    pagination: { pageSize: 20 },
  });

  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = query;
  const loadMoreRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!hasNextPage || isFetchingNextPage) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          fetchNextPage();
        }
      },
      { threshold: 1 }
    );

    const currentRef = loadMoreRef.current;
    if (currentRef) {
      observer.observe(currentRef);
    }

    return () => {
      if (currentRef) {
        observer.unobserve(currentRef);
      }
    };
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  const allProducts = data?.pages.flatMap((page) => page.data) ?? [];

  return (
    <div>
      {allProducts.map((product) => (
        <div key={product.id}>{product.name}</div>
      ))}
      
      <div ref={loadMoreRef}>
        {isFetchingNextPage && <div>Loading more...</div>}
      </div>
    </div>
  );
};

With Filters and Sorting

const { query } = useInfiniteList({
  resource: "posts",
  pagination: { pageSize: 10 },
  filters: [
    {
      field: "status",
      operator: "eq",
      value: "published",
    },
  ],
  sorters: [
    {
      field: "createdAt",
      order: "desc",
    },
  ],
});

Accessing Page Information

const { query } = useInfiniteList({
  resource: "posts",
  pagination: { pageSize: 10 },
});

const { data } = query;

// Access individual pages
data?.pages.forEach((page, index) => {
  console.log(`Page ${index + 1}:`, page.data);
  console.log(`Total:`, page.total);
  console.log(`Pagination:`, page.pagination);
});

// Get total number of items across all pages
const totalItems = data?.pages[0]?.total;

// Get all items flattened
const allItems = data?.pages.flatMap((page) => page.data) ?? [];

Virtual Scrolling with TanStack Virtual

import { useInfiniteList } from "@refinedev/core";
import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef } from "react";

const VirtualList = () => {
  const { query } = useInfiniteList({
    resource: "posts",
    pagination: { pageSize: 50 },
  });

  const { data, fetchNextPage, hasNextPage } = query;
  const allRows = data?.pages.flatMap((page) => page.data) ?? [];

  const parentRef = useRef<HTMLDivElement>(null);

  const rowVirtualizer = useVirtualizer({
    count: hasNextPage ? allRows.length + 1 : allRows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
    overscan: 5,
  });

  useEffect(() => {
    const [lastItem] = [...rowVirtualizer.getVirtualItems()].reverse();

    if (!lastItem) return;

    if (
      lastItem.index >= allRows.length - 1 &&
      hasNextPage &&
      !query.isFetchingNextPage
    ) {
      fetchNextPage();
    }
  }, [
    hasNextPage,
    fetchNextPage,
    allRows.length,
    query.isFetchingNextPage,
    rowVirtualizer.getVirtualItems(),
  ]);

  return (
    <div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
      <div
        style={{
          height: `${rowVirtualizer.getTotalSize()}px`,
          position: "relative",
        }}
      >
        {rowVirtualizer.getVirtualItems().map((virtualRow) => {
          const isLoaderRow = virtualRow.index > allRows.length - 1;
          const post = allRows[virtualRow.index];

          return (
            <div
              key={virtualRow.index}
              style={{
                position: "absolute",
                top: 0,
                left: 0,
                width: "100%",
                height: `${virtualRow.size}px`,
                transform: `translateY(${virtualRow.start}px)`,
              }}
            >
              {isLoaderRow ? "Loading..." : post.title}
            </div>
          );
        })}
      </div>
    </div>
  );
};

Refetching All Pages

const { query } = useInfiniteList({
  resource: "posts",
});

const handleRefresh = () => {
  query.refetch(); // Refetches all loaded pages
};

Type Parameters

  • TQueryFnData - Result data type returned by the query function. Extends BaseRecord.
  • TError - Custom error type that extends HttpError.
  • TData - Result data type returned by the select function. Extends BaseRecord. Defaults to TQueryFnData.

FAQ

Flatten the pages array:
const allItems = data?.pages.flatMap((page) => page.data) ?? [];
  • Load More Button: Call fetchNextPage() on button click
  • Automatic Scroll: Use Intersection Observer to detect when user scrolls near the bottom
Yes, combine with @tanstack/react-virtual for efficient rendering of large lists. See the virtual scrolling example above.
Access the total from the first page:
const totalCount = data?.pages[0]?.total;
Calling refetch() will refetch all currently loaded pages, not just the first page.

Build docs developers (and LLMs) love