useInfiniteGetList

This hook calls dataProvider.getList() when the component mounts. It returns a list of “pages” of records, and a callback to fetch the previous or next page. It’s ideal to render a feed of events or messages, where the total number of records is unknown, and the user requires the next page via a button (or a scroll listener).

It is based on react-query’s useInfiniteQuery hook.

Syntax

useInfiniteGetList works like useGetList, except it returns an object with the following shape:

const {
    data: { pages, pageParams },
    total,
    pageInfo,
    isPending, 
    error,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isFetchingNextPage,
    isFetchingPreviousPage,
} = useInfiniteGetList(
    resource,
    { pagination, sort, filter, meta },
    options
);

The data.pages property is an array records. To render the result of the hook, you must iterate over the pages.

If your data provider doesn’t return the total number of records (see Partial Pagination), this hook automatically uses the pageInfo field to determine if there are more records to fetch.

Usage

For instance, to render the latest news:

import { useInfiniteGetList } from 'react-admin';

const LatestNews = () => {
    const { 
        data,
        total,
        isPending,
        error,
        hasNextPage,
        isFetchingNextPage,
        fetchNextPage,
    } = useInfiniteGetList(
        'posts',
        {
            pagination: { page: 1, perPage: 10 },
            sort: { field: 'published_at', order: 'DESC' }
        }
    );
    if (isPending) { return <p>Loading</p>; }
    if (error) { return <p>ERROR</p>; }

    return (
       <>
           <ul>
               {data?.pages.map(page => 
                    page.data.map(post => 
                        <li key={post.id}>{post.title}</li>
                    )
                )}
           </ul>
           {hasNextPage &&
               <button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>
                   Next page
               </button>
            }
       </>
   );
};

Check react-query’s useInfiniteQuery documentation for more details and examples.

resource

The first parameter of the useInfiniteGetList hook is the name of the resource to fetch.

For instance, to fetch a list of posts:

const { data } = useInfiniteGetList(
    'posts', 
    { pagination: { page: 1, perPage: 10 }, sort: { field: 'published_at', order: 'DESC' } }
);

query

The second parameter is the query passed to dataProvider.getList(). It is an object with the following shape:

{
    pagination: { page, perPage },
    sort: { field, order },
    filter: { ... },
    meta: { ... }
}

The perPage parameter determines the number of records returned in each page.

For instance, to return pages of 25 records each:

const { data } = useInfiniteGetList(
    'posts', 
    { pagination: { page: 1, perPage: 25 }, sort: { field: 'published_at', order: 'DESC' } }
);

Use the meta parameter to pass custom metadata to the data provider. For instance, if the backend suports embedding related records, you can pass the _embed parameter to retrieve them.

const { data } = useInfiniteGetList(
    'posts', 
    { 
        pagination: { page: 1, perPage: 25 },
        sort: { field: 'published_at', order: 'DESC' }, 
        meta: { _embed: ['author', 'tags'] }
    }
);

options

The last argument of the hook contains the query options. It is an object with the following shape:

{
    onSuccess: () => { ... },
    onError: () => { ... },
    enabled,
    ...
}

For instance, to disable the call to the data provider until a condition is met:

const { data } = useInfiniteGetList(
    'posts', 
    { 
        pagination: { page: 1, perPage: 25 }, 
        sort: { field: 'published_at', order: 'DESC' },
        filter: { user_id: user && user.id },
    },
    { enabled: !!user }
);

Additional options are passed to react-query’s useQuery hook. Check the react-query documentation for more information.

Infinite Scrolling

Combining useInfiniteGetList and the Intersection Observer API, you can implement an infinite scrolling list, where the next page loads automatically when the user scrolls down.

import { useRef, useCallback, useEffect } from 'react';
import {
    List,
    ListItem,
    ListItemText,
    ListItemIcon,
    Button,
    Typography,
} from '@mui/material';
import { useInfiniteGetList } from 'react-admin';

const LatestNews = () => {
    const {
        data,
        fetchNextPage,
        hasNextPage,
        isFetchingNextPage,
    } = useInfiniteGetList('posts', {
        pagination: { page: 1, perPage: 10 },
        sort: { field: 'published_at', order: 'DESC' },
    });
    const observerElem = useRef(null);

    const handleObserver = useCallback(
        entries => {
            const [target] = entries;
            if (target.isIntersecting && hasNextPage) {
                fetchNextPage();
            }
        },
        [fetchNextPage, hasNextPage]
    );
    useEffect(() => {
        const element = observerElem.current;
        if (!element) return;
        const option = { threshold: 0 };
        const observer = new IntersectionObserver(handleObserver, option);
        observer.observe(element);
        return () => observer.unobserve(element);
    }, [fetchNextPage, hasNextPage, handleObserver]);

    return (
        <>
            <List dense>
                {data?.pages.map(page => {
                    return page.data.map(post => (
                        <ListItem disablePadding key={post.id}>
                            <ListItemText>
                                {post.title}
                            </ListItemText>
                        </ListItem>
                    ));
                })}
            </List>
            <Typography ref={observerElem} variant="body2" color="grey.500" >
                {isFetchingNextPage && hasNextPage
                    ? 'Loading...'
                    : 'No search left'}
            </Typography>
        </>
    );
};