Skip to content
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
68 changes: 40 additions & 28 deletions docs/src/pages/guides/infinite-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ Rendering lists that can additively "load more" data onto an existing set of dat
When using `useInfiniteQuery`, you'll notice a few things are different:

- `data` is now an array of arrays that contain query group results, instead of the query results themselves
- A `fetchMore` function is now available
- A `getFetchMore` option is available for both determining if there is more data to load and the information to fetch it. This information is supplied as an additional parameter in the query function (which can optionally be overridden when calling the `fetchMore` function)
- A `canFetchMore` boolean is now available and is `true` if `getFetchMore` returns a truthy value
- An `isFetchingMore` boolean is now available to distinguish between a background refresh state and a loading more state
- The `fetchNextPage` and `fetchPreviousPage` functions are now available
- The `getNextPageParam` and `getPreviousPageParam` options are available for both determining if there is more data to load and the information to fetch it. This information is supplied as an additional parameter in the query function (which can optionally be overridden when calling the `fetchNextPage` or `fetchPreviousPage` functions)
- A `hasNextPage` boolean is now available and is `true` if `getNextPageParam` returns a value other than `undefined`.
- A `hasPreviousPage` boolean is now available and is `true` if `getPreviousPageParam` returns a value other than `undefined`.
- The `isFetchingNextPage` and `isFetchingPreviousPage` booleans are now available to distinguish between a background refresh state and a loading more state

## Example

Expand All @@ -31,10 +32,10 @@ fetch('/api/projects?cursor=9')
With this information, we can create a "Load More" UI by:

- Waiting for `useInfiniteQuery` to request the first group of data by default
- Returning the information for the next query in `getFetchMore`
- Calling `fetchMore` function
- Returning the information for the next query in `getNextPageParam`
- Calling `fetchNextPage` function

> Note: It's very important you do not call `fetchMore` with arguments unless you want them to override the `fetchMoreInfo` data returned from the `getFetchMore` function. eg. Do not do this: `<button onClick={fetchMore} />` as this would send the onClick event to the `fetchMore` function.
> Note: It's very important you do not call `fetchNextPage` with arguments unless you want them to override the `pageParam` data returned from the `getNextPageParam` function. eg. Do not do this: `<button onClick={fetchNextPage} />` as this would send the onClick event to the `fetchNextPage` function.

```js
import { useInfiniteQuery } from 'react-query'
Expand All @@ -44,14 +45,14 @@ function Projects() {
fetch('/api/projects?cursor=' + cursor)

const {
status,
data,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
isFetchingNextPage,
status,
} = useInfiniteQuery('projects', fetchProjects, {
getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})

return status === 'loading' ? (
Expand All @@ -69,17 +70,17 @@ function Projects() {
))}
<div>
<button
onClick={() => fetchMore()}
disabled={!canFetchMore || isFetchingMore}
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingMore
{isFetchingNextPage
? 'Loading more...'
: canFetchMore
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingMore ? 'Fetching...' : null}</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)
}
Expand All @@ -91,7 +92,7 @@ When an infinite query becomes `stale` and needs to be refetched, each group is

## What if I need to pass custom information to my query function?

By default, the info returned from `getFetchMore` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchMore` function which will override the default info like so:
By default, the variable returned from `getNextPageParam` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchNextPage` function which will override the default variable like so:

```js
function Projects() {
Expand All @@ -102,24 +103,35 @@ function Projects() {
status,
data,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
getFetchMore: (lastGroup, allGroups) => lastGroup.nextCursor,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})

// Pass your own custom fetchMoreInfo
const skipToCursor50 = () => fetchMore(50)
// Pass your own page param
const skipToCursor50 = () => fetchNextPage({ pageParam: 50 })
}
```

## What if I want to infinitely load more data in reverse?
## What if I want to implement a bi-directional infinite list?

Sometimes you may not want to **append** infinitely loaded data, but instead **prepend** it. If this is case, you can use `fetchMore`'s `previous` option, eg.
Bi-directional lists can be implemented by using the `getPreviousPageParam`, `fetchPreviousPage`, `hasPreviousPage` and `isFetchingPreviousPage` properties and functions.

```js
fetchMore(previousPageVariables, { previous: true })
useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
})
```

This will ensure the new data is prepended to the data array instead of appended.
## What if I want to show the pages in reversed order?

Sometimes you may want to show the pages in reversed order. If this is case, you can use the `select` option:

```js
useInfiniteQuery('projects', fetchProjects, {
select: pages => [...pages].reverse(),
})
```
92 changes: 92 additions & 0 deletions docs/src/pages/guides/migrating-to-react-query-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,98 @@ function Page({ page }) {
}
```

### useInfiniteQuery()

The `useInfiniteQuery()` interface has changed to fully support bi-directional infinite lists.

One direction:

```js
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
```

Both directions:

```js
const {
data,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
})
```

One direction reversed:

```js
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
select: pages => [...pages].reverse(),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
```

### useMutation()

The `useMutation()` hook now returns an object instead of an array:

```js
// Old:
const [mutate, { status, reset }] = useMutation()

// New:
const { mutate, status, reset } = useMutation()
```

Previously the `mutate` function returned a promise which resolved to `undefined` if a mutation failed instead of throwing.
We got a lot of questions regarding this behavior as users expected the promise to behave like a regular promise.
Because of this the `mutate` function is now split into a `mutate` and `mutateAsync` function.

The `mutate` function can be used when using callbacks:

```js
const { mutate } = useMutation(addTodo)

mutate('todo', {
onSuccess: data => {
console.log(data)
},
onError: error => {
console.error(error)
},
})
```

The `mutateAsync` function can be used when using async/await:

```js
const { mutateAsync } = useMutation(addTodo)

try {
const data = await mutateAsync('todo')
console.log(data)
} catch (error) {
console.error(error)
}
```

### Query object syntax

The object syntax has been collapsed:
Expand Down
42 changes: 29 additions & 13 deletions docs/src/pages/reference/useInfiniteQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,51 @@ title: useInfiniteQuery

```js

const queryFn = (...queryKey, fetchMoreVariable) // => Promise
const queryFn = (...queryKey, pageParam) // => Promise

const {
isFetchingMore,
fetchMore,
canFetchMore,
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery(queryKey, queryFn, {
...options,
getFetchMore: (lastPage, allPages) => fetchMoreVariable
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor
})
```

**Options**

The options for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery) with the addition of the following:

- `getFetchMore: (lastPage, allPages) => fetchMoreVariable | boolean`
- `getNextPageParam: (lastPage, allPages) => unknown | undefined`
- When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages.
- It should return a **single variable** that will be passed as the last optional parameter to your query function
- It should return a **single variable** that will be passed as the last optional parameter to your query function.
- Return `undefined` to indicate there is no next page available.
- `getPreviousPageParam: (firstPage, allPages) => unknown | undefined`
- When new data is received for this query, this function receives both the first page of the infinite list of data and the full array of all pages.
- It should return a **single variable** that will be passed as the last optional parameter to your query function.
- Return `undefined` to indicate there is no previous page available.

**Returns**

The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery), with the addition of the following:

- `isFetchingMore: false | 'next' | 'previous'`
- If using `paginated` mode, this will be `true` when fetching more results using the `fetchMore` function.
- `fetchMore: (fetchMoreVariableOverride) => Promise<UseInfiniteQueryResult>`
- `isFetchingNextPage: boolean`
- Will be `true` while fetching the next page with `fetchNextPage`.
- `isFetchingPreviousPage: boolean`
- Will be `true` while fetching the previous page with `fetchPreviousPage`.
- `fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult>`
- This function allows you to fetch the next "page" of results.
- `fetchMoreVariableOverride` allows you to optionally override the fetch more variable returned from your `getFetchMore` option to your query function to retrieve the next page of results.
- `canFetchMore: boolean`
- If using `paginated` mode, this will be `true` if there is more data to be fetched (known via the required `getFetchMore` option function).
- `options.pageParam: unknown` allows you to manually specify a page param instead of using `getNextPageParam`.
- `fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult>`
- This function allows you to fetch the previous "page" of results.
- `options.pageParam: unknown` allows you to manually specify a page param instead of using `getPreviousPageParam`.
- `hasNextPage: boolean`
- This will be `true` if there is a next page to be fetched (known via the `getNextPageParam` option).
- `hasPreviousPage: boolean`
- This will be `true` if there is a previous page to be fetched (known via the `getPreviousPageParam` option).
2 changes: 1 addition & 1 deletion examples/basic-graphql-request/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function usePost(postId) {
return post;
},
{
enabled: postId,
enabled: !!postId,
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const getPostById = async (key, id) => {

function usePost(postId) {
return useQuery(["post", postId], getPostById, {
enabled: postId,
enabled: !!postId,
});
}

Expand Down
2 changes: 1 addition & 1 deletion examples/default-query-function/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function Posts({ setPostId }) {
function Post({ postId, setPostId }) {
// You can even leave out the queryFn and just go straight into options
const { status, data, error, isFetching } = useQuery(`/posts/${postId}`, {
enabled: postId,
enabled: !!postId,
});

return (
Expand Down
28 changes: 15 additions & 13 deletions examples/load-more-infinite-scroll/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ function Example() {
data,
error,
isFetching,
isFetchingMore,
fetchMore,
canFetchMore,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useInfiniteQuery(
'projects',
async (key, nextId = 0) => {
const { data } = await axios.get('/api/projects?cursor=' + nextId)
return data
async (_key, nextId = 0) => {
const res = await axios.get('/api/projects?cursor=' + nextId)
return res.data
},
{
getFetchMore: lastGroup => lastGroup.nextId,
Expand All @@ -49,8 +49,8 @@ function Example() {

useIntersectionObserver({
target: loadMoreButtonRef,
onIntersect: fetchMore,
enabled: canFetchMore,
onIntersect: fetchNextPage,
enabled: hasNextPage,
})

return (
Expand Down Expand Up @@ -81,18 +81,20 @@ function Example() {
<div>
<button
ref={loadMoreButtonRef}
onClick={() => fetchMore()}
disabled={!canFetchMore || isFetchingMore}
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingMore
{isFetchingNextPage
? 'Loading more...'
: canFetchMore
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>
{isFetching && !isFetchingMore ? 'Background Updating...' : null}
{isFetching && !isFetchingNextPage
? 'Background Updating...'
: null}
</div>
</>
)}
Expand Down
Loading