Skip to content

Commit

Permalink
feat: add query function context (#1261)
Browse files Browse the repository at this point in the history
  • Loading branch information
boschni committed Nov 6, 2020
1 parent fca1f5a commit 9cef1ea
Show file tree
Hide file tree
Showing 24 changed files with 191 additions and 173 deletions.
8 changes: 4 additions & 4 deletions docs/src/pages/guides/infinite-queries.md
Expand Up @@ -43,8 +43,8 @@ With this information, we can create a "Load More" UI by:
import { useInfiniteQuery } from 'react-query'

function Projects() {
const fetchProjects = (key, cursor = 0) =>
fetch('/api/projects?cursor=' + cursor)
const fetchProjects = ({ pageParam = 0 }) =>
fetch('/api/projects?cursor=' + pageParam)

const {
data,
Expand Down Expand Up @@ -98,8 +98,8 @@ By default, the variable returned from `getNextPageParam` will be supplied to th

```js
function Projects() {
const fetchProjects = (key, cursor = 0) =>
fetch('/api/projects?cursor=' + cursor)
const fetchProjects = ({ pageParam = 0 }) =>
fetch('/api/projects?cursor=' + pageParam)

const {
status,
Expand Down
72 changes: 58 additions & 14 deletions docs/src/pages/guides/migrating-to-react-query-3.md
Expand Up @@ -65,6 +65,32 @@ const queryClient = new QueryClient({
})
```

### Query function parameters

Query functions now get a `QueryFunctionContext` instead of the query key parameters.

The `QueryFunctionContext` contains a `queryKey` and a `pageParam` in case of ininite queries.

useQuery:

```js
// Old
useQuery(['post', id], (_key, id) => fetchPost(id))

// New
useQuery(['post', id], () => fetchPost(id))
```

useInfiniteQuery:

```js
// Old
useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))

// New
useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPost(pageParam))
```

### usePaginatedQuery()

The `usePaginatedQuery()` hook has been replaced by the `keepPreviousData` option on `useQuery`:
Expand Down Expand Up @@ -93,9 +119,13 @@ const {
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
} = useInfiniteQuery(
'projects',
({ pageParam = 0 }) => fetchProjects(pageParam),
{
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
}
)
```

Both directions:
Expand All @@ -109,10 +139,14 @@ const {
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
} = useInfiniteQuery('projects', fetchProjects, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
})
} = useInfiniteQuery(
'projects',
({ pageParam = 0 }) => fetchProjects(pageParam),
{
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
}
)
```

One direction reversed:
Expand All @@ -123,13 +157,17 @@ const {
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('projects', fetchProjects, {
select: data => ({
pages: [...data.pages].reverse(),
pageParams: [...data.pageParams].reverse(),
}),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
} = useInfiniteQuery(
'projects',
({ pageParam = 0 }) => fetchProjects(pageParam),
{
select: data => ({
pages: [...data.pages].reverse(),
pageParams: [...data.pageParams].reverse(),
}),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
}
)
```

Manually removing the first page:
Expand Down Expand Up @@ -276,6 +314,12 @@ The `forceFetchOnMount` query option has been replaced by `refetchOnMount: 'alwa
When `refetchOnMount` was set to `false` any additional components were prevented from refetching on mount.
In version 3 only the component where the option has been set will not refetch on mount.
### QueryOptions.queryFnParamsFilter
The `queryFnParamsFilter` option has been removed because query functions now get a `QueryFunctionContext` object instead of the query key.
Parameters can still be filtered within the query function itself as the `QueryFunctionContext` also contains the query key.
### QueryResult.clear()
The `QueryResult.clear()` method has been renamed to `QueryResult.remove()`.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/pages/guides/paginated-queries.md
Expand Up @@ -27,7 +27,7 @@ Consider the following example where we would ideally want to increment a pageIn
function Todos() {
const [page, setPage] = React.useState(0)

const fetchProjects = (key, page = 0) => fetch('/api/projects?page=' + page)
const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page)

const {
isLoading,
Expand All @@ -36,7 +36,7 @@ function Todos() {
data,
isFetching,
isPreviousData,
} = useQuery(['projects', page], fetchProjects)
} = useQuery(['projects', page], () => fetchProjects(page))

return (
<div>
Expand Down
20 changes: 3 additions & 17 deletions docs/src/pages/guides/query-functions.md
Expand Up @@ -46,31 +46,17 @@ useQuery(['todos', todoId], async () => {

## Query Function Variables

Query keys are not just for uniquely identifying the data you are fetching, but are also conveniently passed as variables for your query function and while not always necessary, this makes it possible to extract your query functions if needed. The individual parts of the query key get passed through to your query function as parameters in the same order they appear in the array key:
Query keys are not just for uniquely identifying the data you are fetching, but are also conveniently passed into your query function and while not always necessary, this makes it possible to extract your query functions if needed:

```js
function Todos({ completed }) {
const result = useQuery(['todos', { status, page }], fetchTodoList)
}

// Access the key, status and page variables in your query function!
function fetchTodoList(key, { status, page }) {
function fetchTodoList({ queryKey }) {
const { status, page } = queryKey[1]
return new Promise()
// ...
}
```

If you send through more items in your query key, they will also be available in your query function:

```js
function Todo({ todoId, preview }) {
const result = useQuery(['todo', todoId, { preview }], fetchTodoById)
}

// Access status and page in your query function!
function fetchTodoById(key, todoId, { preview }) {
return new Promise()
// ...
}
```

Expand Down
14 changes: 9 additions & 5 deletions docs/src/pages/reference/useInfiniteQuery.md
Expand Up @@ -4,9 +4,6 @@ title: useInfiniteQuery
---

```js

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

const {
fetchNextPage,
fetchPreviousPage,
Expand All @@ -15,17 +12,24 @@ const {
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery(queryKey, queryFn, {
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
...options,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
```

**Options**

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

- `queryFn: (context: QueryFunctionContext) => Promise<TData>`
- **Required, but only if no default query function has been defined**
- The function that the query will use to request data.
- Receives a `QueryFunctionContext` object with the following variables:
- `queryKey: QueryKey`
- `pageParam: unknown | undefined`
- Must return a promise that will either resolves data or throws an error.
- `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.
Expand Down
15 changes: 7 additions & 8 deletions docs/src/pages/reference/useQuery.md
Expand Up @@ -32,7 +32,6 @@ const {
onError,
onSettled,
onSuccess,
queryFnParamsFilter,
queryKeyHashFn,
refetchInterval,
refetchIntervalInBackground,
Expand Down Expand Up @@ -63,11 +62,11 @@ const result = useQuery({
- The query key to use for this query.
- The query key will be hashed into a stable hash. See [Query Keys](./guides/query-keys) for more information.
- The query will automatically update when this key changes (as long as `enabled` is not set to `false`).
- `queryFn: (...params: unknown[]) => Promise<TData>`
- `queryFn: (context: QueryFunctionContext) => Promise<TData>`
- **Required, but only if no default query function has been defined**
- The function that the query will use to request data.
- Receives the following variables in the order that they are provided:
- Query Key parameters
- Receives a `QueryFunctionContext` object with the following variables:
- `queryKey: QueryKey`
- Must return a promise that will either resolves data or throws an error.
- `enabled: boolean`
- Set this to `false` to disable this query from automatically running.
Expand Down Expand Up @@ -149,10 +148,6 @@ const result = useQuery({
- Optional
- Defaults to `false`
- If set, any previous `data` will be kept when fetching new data because the query key changed.
- `queryFnParamsFilter: (...params: unknown[]) => unknown[]`
- Optional
- This function will filter the params that get passed to `queryFn`.
- For example, you can filter out the first query key from the params by using `queryFnParamsFilter: params => params.slice(1)`.
- `structuralSharing: boolean`
- Optional
- Defaults to `true`
Expand All @@ -174,6 +169,10 @@ const result = useQuery({
- A derived boolean from the `status` variable above, provided for convenience.
- `isError: boolean`
- A derived boolean from the `status` variable above, provided for convenience.
- `isLoadingError: boolean`
- Will be `true` if the query failed while fetching for the first time.
- `isRefetchError: boolean`
- Will be `true` if the query failed while refetching.
- `data: TData`
- Defaults to `undefined`.
- The last successfully resolved data for the query.
Expand Down
4 changes: 2 additions & 2 deletions examples/basic/src/index.js
Expand Up @@ -90,15 +90,15 @@ function Posts({ setPostId }) {
);
}

const getPostById = async (key, id) => {
const getPostById = async (id) => {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
return data;
};

function usePost(postId) {
return useQuery(["post", postId], getPostById, {
return useQuery(["post", postId], () => getPostById(postId), {
enabled: !!postId,
});
}
Expand Down
4 changes: 2 additions & 2 deletions examples/custom-hooks/src/hooks/usePost.js
@@ -1,13 +1,13 @@
import { useQuery } from "react-query";
import axios from "axios";

const getPostById = async (_, postId) => {
const getPostById = async (postId) => {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${postId}`
);
return data;
};

export default function usePost(postId) {
return useQuery(["post", postId], getPostById);
return useQuery(["post", postId], () => getPostById(postId));
}
4 changes: 2 additions & 2 deletions examples/default-query-function/src/index.js
Expand Up @@ -11,9 +11,9 @@ import {
import { ReactQueryDevtools } from "react-query-devtools";

// Define a default query function that will receive the query key
const defaultQueryFn = async (key) => {
const defaultQueryFn = async ({ queryKey }) => {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com${key}`
`https://jsonplaceholder.typicode.com${queryKey[0]}`
);
return data;
};
Expand Down
4 changes: 2 additions & 2 deletions examples/load-more-infinite-scroll/pages/index.js
Expand Up @@ -30,8 +30,8 @@ function Example() {
hasNextPage,
} = useInfiniteQuery(
'projects',
async (_key, nextId = 0) => {
const res = await axios.get('/api/projects?cursor=' + nextId)
async ({ pageParam = 0 }) => {
const res = await axios.get('/api/projects?cursor=' + pageParam)
return res.data
},
{
Expand Down
8 changes: 3 additions & 5 deletions examples/nextjs/hooks/usePosts/index.js
@@ -1,16 +1,14 @@
import ky from 'ky-universal'
import { useQuery } from 'react-query'

const fetchPosts = async (_, limit) => {
const offset = limit ?? 10

const fetchPosts = async (limit = 10) => {
const parsed = await ky('https://jsonplaceholder.typicode.com/posts').json()
const result = parsed.filter(x => x.id <= offset)
const result = parsed.filter(x => x.id <= limit)
return result
}

const usePosts = limit => {
return useQuery(['posts', limit], fetchPosts)
return useQuery(['posts', limit], () => fetchPosts(limit))
}

export { usePosts, fetchPosts }
4 changes: 2 additions & 2 deletions examples/pagination/pages/index.js
Expand Up @@ -22,14 +22,14 @@ function Example() {
const queryClient = useQueryClient()
const [page, setPage] = React.useState(0)

const fetchProjects = React.useCallback(async (key, page = 0) => {
const fetchProjects = React.useCallback(async (page = 0) => {
const { data } = await axios.get('/api/projects?page=' + page)
return data
}, [])

const { status, data, error, isFetching, isPreviousData } = useQuery(
['projects', page],
fetchProjects,
() => fetchProjects(page),
{ keepPreviousData: true }
)

Expand Down
8 changes: 4 additions & 4 deletions examples/playground/src/index.js
Expand Up @@ -181,7 +181,7 @@ function Todos({ initialFilter = "", setEditingIndex }) {

const { status, data, isFetching, error, failureCount, refetch } = useQuery(
["todos", { filter }],
fetchTodos
() => fetchTodos({ filter })
);

return (
Expand Down Expand Up @@ -235,7 +235,7 @@ function EditTodo({ editingIndex, setEditingIndex }) {
// Don't attempt to query until editingIndex is truthy
const { status, data, isFetching, error, failureCount, refetch } = useQuery(
["todo", { id: editingIndex }],
fetchTodoById,
() => fetchTodoById({ id: editingIndex }),
{
enabled: editingIndex !== null,
}
Expand Down Expand Up @@ -370,7 +370,7 @@ function AddTodo() {
);
}

function fetchTodos(key, { filter } = {}) {
function fetchTodos({ filter } = {}) {
console.info("fetchTodos", { filter });
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
Expand All @@ -388,7 +388,7 @@ function fetchTodos(key, { filter } = {}) {
return promise;
}

function fetchTodoById(key, { id }) {
function fetchTodoById({ id }) {
console.info("fetchTodoById", { id });
return new Promise((resolve, reject) => {
setTimeout(() => {
Expand Down

0 comments on commit 9cef1ea

Please sign in to comment.