Skip to content

Commit

Permalink
Merge pull request #8063 from marmelab/useInfiniteGetList
Browse files Browse the repository at this point in the history
Add useInfiniteGetList hook
  • Loading branch information
slax57 authored Aug 31, 2022
2 parents 0d45da5 + eb8d7bc commit 4463c94
Show file tree
Hide file tree
Showing 9 changed files with 2,103 additions and 0 deletions.
Binary file added docs/img/useInfiniteGetList.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<li {% if page.path == 'Actions.md' %} class="active" {% endif %}><a class="nav-link" href="./Actions.html">Querying the API</a></li>
<li {% if page.path == 'useDataProvider.md' %} class="active" {% endif %}><a class="nav-link" href="./useDataProvider.html"><code>useDataProvider</code></a></li>
<li {% if page.path == 'useGetList.md' %} class="active" {% endif %}><a class="nav-link" href="./useGetList.html"><code>useGetList</code></a></li>
<li {% if page.path == 'useInfiniteGetList.md' %} class="active" {% endif %}><a class="nav-link" href="./useInfiniteGetList.html"><code>useInfiniteGetList</code></a></li>
<li {% if page.path == 'useGetOne.md' %} class="active" {% endif %}><a class="nav-link" href="./useGetOne.html"><code>useGetOne</code></a></li>
<li {% if page.path == 'useGetMany.md' %} class="active" {% endif %}><a class="nav-link" href="./useGetMany.html"><code>useGetMany</code></a></li>
<li {% if page.path == 'useGetManyReference.md' %} class="active" {% endif %}><a class="nav-link" href="./useGetManyReference.html"><code>useGetManyReference</code></a></li>
Expand Down
235 changes: 235 additions & 0 deletions docs/useInfiniteGetList.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
---
layout: default
title: "useInfiniteGetList"
---

# `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).

![useInfiniteGetList](./img/useInfiniteGetList.gif)

It is based on react-query's [`useInfiniteQuery`](https://react-query-v3.tanstack.com/reference/useInfiniteQuery) hook.

## Syntax

`useInfiniteGetList` works like [`useGetList`](./useGetList.md), except it returns an object with the following shape:

```jsx
const {
data: { pages, pageParams },
total,
pageInfo,
isLoading,
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](./DataProviderWriting.md#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:

```jsx
import { useInfinteGetList } from 'react-admin';

const LatestNews = () => {
const {
data,
total,
isLoading,
error,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = useInfiniteGetList(
'posts',
{
pagination: { page: 1, perPage: 10 },
sort: { field: 'published_at', order: 'DESC' }
}
);
if (isLoading) { 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](https://react-query-v3.tanstack.com/reference/useInfiniteQuery) 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:
```jsx
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:
```jsx
{
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:
```jsx
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.
```jsx
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:
```jsx
{
onSuccess: () => { ... },
onError: () => { ... },
enabled,
...
}
```
For instance, to disable the call to the data provider until a condition is met:
```jsx
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](https://react-query-v3.tanstack.com/reference/useQuery) for more information.
## Infinite Scrolling
Combining `useInfiniteGetList` and [the Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), you can implement an infinite scrolling list, where the next page loads automatically when the user scrolls down.
```jsx
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>
</>
);
};
```
1 change: 1 addition & 0 deletions packages/ra-core/src/dataProvider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from './useUpdate';
export * from './useUpdateMany';
export * from './useDelete';
export * from './useDeleteMany';
export * from './useInfiniteGetList';

export type { Options } from './fetch';

Expand Down
Loading

0 comments on commit 4463c94

Please sign in to comment.