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
10 changes: 10 additions & 0 deletions docs/src/manifests/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,16 @@
"path": "/reference/QueryCache",
"editUrl": "/reference/QueryCache.md"
},
{
"title": "QueryObserver",
"path": "/reference/QueryObserver",
"editUrl": "/reference/QueryObserver.md"
},
{
"title": "QueriesObserver",
"path": "/reference/QueriesObserver",
"editUrl": "/reference/QueriesObserver.md"
},
{
"title": "QueryErrorResetBoundary",
"path": "/reference/QueryErrorResetBoundary",
Expand Down
8 changes: 4 additions & 4 deletions docs/src/pages/guides/default-query-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const defaultQueryFn = async key => {
}

// provide the default query function to your app with defaultOptions
const cache = new QueryCache()
const client = new QueryClient({
cache,
const queryCache = new QueryCache()
const queryClient = new QueryClient({
queryCache,
defaultOptions: {
queries: {
queryFn: defaultQueryFn,
Expand All @@ -25,7 +25,7 @@ const client = new QueryClient({

function App() {
return (
<QueryClientProvider client={client}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
)
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/guides/infinite-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function Projects() {

## What happens when an infinite query needs to be refetched?

When an infinite query becomes `stale` and needs to be refetched, each group is fetched `sequentially`, starting from the first one. This ensures that even if the underlying data is mutated we're not using stale cursors and potentially getting duplicates or skipping records. If an infinite query's results are ever removed from the cache, the pagination restarts at the initial state with only the initial group being requested.
When an infinite query becomes `stale` and needs to be refetched, each group is fetched `sequentially`, starting from the first one. This ensures that even if the underlying data is mutated we're not using stale cursors and potentially getting duplicates or skipping records. If an infinite query's results are ever removed from the queryCache, the pagination restarts at the initial state with only the initial group being requested.

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

Expand Down
6 changes: 3 additions & 3 deletions docs/src/pages/guides/initial-query-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,20 @@ function Todo({ todoId }) {
const result = useQuery(['todo', todoId], () => fetch('/todos'), {
initialData: () => {
// Use a todo from the 'todos' query as the initial data for this todo query
return client.getQueryData('todos')?.find(d => d.id === todoId)
return queryClient.getQueryData('todos')?.find(d => d.id === todoId)
},
})
}
```

Most of the time, this pattern works well, but if the source query you're using to look up the initial data from is old, you may not want to use the data at all and just fetch from the server. To make this decision easier, you can use the `client.getQueryState` method instead to get more information about the source query, including a `state.updatedAt` timestamp you can use to decide if the query is "fresh" enough for your needs:
Most of the time, this pattern works well, but if the source query you're using to look up the initial data from is old, you may not want to use the data at all and just fetch from the server. To make this decision easier, you can use the `queryClient.getQueryState` method instead to get more information about the source query, including a `state.updatedAt` timestamp you can use to decide if the query is "fresh" enough for your needs:

```js
function Todo({ todoId }) {
const result = useQuery(['todo', todoId], () => fetch('/todos'), {
initialData: () => {
// Get the query state
const state = client.getQueryState('todos')
const state = queryClient.getQueryState('todos')

// If the query exists and has data that is no older than 10 seconds...
if (state && Date.now() - state.updatedAt <= 10 * 1000) {
Expand Down
6 changes: 3 additions & 3 deletions docs/src/pages/guides/invalidations-from-mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ When a successful `postTodo` mutation happens, we likely want all `todos` querie
```js
import { useMutation, useQueryClient } from 'react-query'

const client = useQueryClient()
const queryClient = useQueryClient()

// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation(addTodo, {
onSuccess: () => {
client.invalidateQueries('todos')
client.invalidateQueries('reminders')
queryClient.invalidateQueries('todos')
queryClient.invalidateQueries('reminders')
},
})
```
Expand Down
72 changes: 49 additions & 23 deletions docs/src/pages/guides/migrating-to-react-query-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ This article explains how to migrate your application to React Query 3.

### QueryClient

The `QueryCache` has been split into a `QueryClient` and a `QueryCache`.
The `QueryCache` contains all cached queries and the `QueryClient` can be used to interact with a cache.
The `QueryCache` has been split into a `QueryClient`, `QueryCache` and `MutationCache`.
The `QueryCache` contains all queries, the `MutationCache` contains all mutations, and the `QueryClient` can be used to set configuration and to interact with them.

This has some benefits:

Expand All @@ -25,11 +25,17 @@ Use the `QueryClientProvider` component to connect a `QueryClient` to your appli
```js
import { QueryClient, QueryClientProvider, QueryCache } from 'react-query'

const cache = new QueryCache()
const client = new QueryClient({ cache })
// Create query cache
const queryCache = new QueryCache()

// Create mutation cache (can be omitted to reduce file size when not using mutations)
const mutationCache = new MutationCache()

// Create client
const queryClient = new QueryClient({ queryCache, mutationCache })

function App() {
return <QueryClientProvider client={client}>...</QueryClientProvider>
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
```

Expand All @@ -42,10 +48,10 @@ import { useCallback } from 'react'
import { useQueryClient } from 'react-query'

function Todo() {
const client = useQueryClient()
const queryClient = useQueryClient()

const onClickButton = useCallback(() => {
client.invalidateQueries('posts')
queryClient.invalidateQueries('posts')
}, [client])

return <button onClick={onClickButton}>Refetch</button>
Expand All @@ -57,8 +63,8 @@ function Todo() {
The `ReactQueryConfigProvider` component has been removed. Default options for queries and mutations can now be specified in `QueryClient`:

```js
const client = new QueryClient({
cache,
const queryClient = new QueryClient({
queryCache,
defaultOptions: {
queries: {
staleTime: Infinity,
Expand Down Expand Up @@ -157,6 +163,9 @@ mutate('todo', {
onError: error => {
console.error(error)
},
onSettled: () => {
console.log('settled)
},
})
```

Expand All @@ -170,9 +179,14 @@ try {
console.log(data)
} catch (error) {
console.error(error)
} finally {
console.log('settled)
}
```

Callbacks passed to the `mutate` or `mutateAsync` functions will now override the callbacks defined on `useMutation`.
The `mutateAsync` function can be used to compose side effects.

### Query object syntax

The object syntax has been collapsed:
Expand All @@ -195,17 +209,17 @@ useQuery({

### queryCache.prefetchQuery()

The `client.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.
The `queryClient.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.

Use the `client.fetchQueryData()` method to get the query data or error:
Use the `queryClient.fetchQueryData()` method to get the query data or error:

```js
// Prefetch a query:
await client.prefetchQuery('posts', fetchPosts)
await queryClient.prefetchQuery('posts', fetchPosts)

// Fetch a query:
try {
const data = await client.fetchQueryData('posts', fetchPosts)
const data = await queryClient.fetchQueryData('posts', fetchPosts)
} catch (error) {
// Error handling
}
Expand Down Expand Up @@ -237,7 +251,7 @@ The `queryCache.getQueries()` method has been replaced by `cache.findAll()`.

### queryCache.isFetching

The `queryCache.isFetching` property has been replaced by `client.isFetching()`.
The `queryCache.isFetching` property has been replaced by `queryClient.isFetching()`.

### QueryOptions.enabled

Expand Down Expand Up @@ -336,25 +350,25 @@ function Overview() {
}
```

#### client.watchQuery()
#### QueryObserver

The `client.watchQuery()` method can be used to create and/or watch a query:
A `QueryObserver` can be used to create and/or watch a query:

```js
const observer = client.watchQuery('posts')
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })

const unsubscribe = observer.subscribe(result => {
console.log(result)
unsubscribe()
})
```

#### client.watchQueries()
#### QueriesObserver

The `client.watchQueries()` method can be used to create and/or watch multiple queries:
A `QueriesObserver` can be used to create and/or watch multiple queries:

```js
const observer = client.watchQueries([
const observer = new QueriesObserver(queryClient, [
{ queryKey: ['post', 1], queryFn: fetchPost },
{ queryKey: ['post', 2], queryFn: fetchPost },
])
Expand All @@ -365,18 +379,30 @@ const unsubscribe = observer.subscribe(result => {
})
```

## `client.setQueryDefaults`
## `queryClient.setQueryDefaults`

The `client.setQueryDefaults()` method to set default options for a specific query. If the query does not exist yet it will create it.
The `queryClient.setQueryDefaults()` method can be used to set default options for specific queries:

```js
client.setQueryDefaults('posts', fetchPosts)
queryClient.setQueryDefaults('posts', { queryFn: fetchPosts })

function Component() {
const { data } = useQuery('posts')
}
```

## `queryClient.setMutationDefaults`

The `queryClient.setMutationDefaults()` method can be used to set default options for specific mutations:

```js
queryClient.setMutationDefaults('addPost', { mutationFn: addPost })

function Component() {
const { mutate } = useMutation('addPost')
}
```

#### useIsFetching()

The `useIsFetching()` hook now accepts filters which can be used to for example only show a spinner for certain type of queries:
Expand Down
48 changes: 26 additions & 22 deletions docs/src/pages/guides/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Beyond those primary state, more information is available depending on the state

In the example above, you also saw that you can pass variables to your mutations function by calling the `mutate` function with a **single variable or object**.

Even with just variables, mutations aren't all that special, but when used with the `onSuccess` option, the [Query Client's `invalidateQueries` method](../reference/QueryClient#clientinvalidatequeries) and the [Query Client's `setQueryData` method](../reference/QueryClient#clientsetquerydata), mutations become a very powerful tool.
Even with just variables, mutations aren't all that special, but when used with the `onSuccess` option, the [Query Client's `invalidateQueries` method](../reference/QueryClient#queryclientinvalidatequeries) and the [Query Client's `setQueryData` method](../reference/QueryClient#queryclientsetquerydata), mutations become a very powerful tool.

> IMPORTANT: The `mutate` function is an asynchronous function, which means you cannot use it directly in an event callback. If you need to access the event in `onSubmit` you need to wrap `mutate` in another function. This is due to [React event pooling](https://reactjs.org/docs/events.html#event-pooling).

Expand Down Expand Up @@ -120,18 +120,12 @@ useMutation(addTodo, {
onMutate: variables => {
// A mutation is about to happen!

// Optionally return a context object with a rollback function
return {
rollback: () => {
// do some rollback logic
},
}
// Optionally return a context containing data to use when for example rolling back
return { id: 1 }
},
onError: (error, variables, context) => {
// An error happened!
if (context.rollback) {
context.rollback()
}
console.log(`rolling back optimistic update with id ${context.id}`)
},
onSuccess: (data, variables, context) => {
// Boom baby!
Expand All @@ -155,41 +149,37 @@ useMutation(addTodo, {
})
```

You might find that you want to **add additional side-effects** to some of the `useMutation` lifecycle at the time of calling `mutate`. To do that, you can provide any of the same callback options to the `mutate` function after your mutation variable. Supported option overrides include:

- `onSuccess` - Will be fired after the `useMutation`-level `onSuccess` handler
- `onError` - Will be fired after the `useMutation`-level `onError` handler
- `onSettled` - Will be fired after the `useMutation`-level `onSettled` handler
You might find that you want to **trigger different callbacks** then the ones defined on `useMutation` when calling `mutate`. To do that, you can provide any of the same callback options to the `mutate` function after your mutation variable. Supported overrides include: `onSuccess`, `onError` and `onSettled`.

```js
useMutation(addTodo, {
onSuccess: (data, variables, context) => {
// I will fire first
// I will not fire
},
onError: (error, variables, context) => {
// I will fire first
// I will not fire
},
onSettled: (data, error, variables, context) => {
// I will fire first
// I will not fire
},
})

mutate(todo, {
onSuccess: (data, variables, context) => {
// I will fire second!
// I will fire instead!
},
onError: (error, variables, context) => {
// I will fire second!
// I will fire instead!
},
onSettled: (data, error, variables, context) => {
// I will fire second!
// I will fire instead!
},
})
```

## Promises

Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error:
Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error. This can for example be used to compose side effects.

```js
const mutation = useMutation(addTodo)
Expand All @@ -199,5 +189,19 @@ try {
console.log(todo)
} catch (error) {
console.error(error)
} finally {
console.log('done')
}
```

## Retry

By default React Query will not retry a mutation on error, but it is possible with the `retry` option:

```js
const mutation = useMutation(addTodo, {
retry: 3,
})
```

If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.
Loading