Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useInfiniteGetList hook #8063

Merged
merged 10 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -22,6 +22,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