Skip to content

Commit eb5078b

Browse files
authored
feat: move mutations into the core (#1162)
1 parent 7abc26a commit eb5078b

File tree

90 files changed

+2903
-1631
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+2903
-1631
lines changed

docs/src/manifests/manifest.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,16 @@
332332
"path": "/reference/QueryCache",
333333
"editUrl": "/reference/QueryCache.md"
334334
},
335+
{
336+
"title": "QueryObserver",
337+
"path": "/reference/QueryObserver",
338+
"editUrl": "/reference/QueryObserver.md"
339+
},
340+
{
341+
"title": "QueriesObserver",
342+
"path": "/reference/QueriesObserver",
343+
"editUrl": "/reference/QueriesObserver.md"
344+
},
335345
{
336346
"title": "QueryErrorResetBoundary",
337347
"path": "/reference/QueryErrorResetBoundary",

docs/src/pages/guides/default-query-function.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ const defaultQueryFn = async key => {
1313
}
1414

1515
// provide the default query function to your app with defaultOptions
16-
const cache = new QueryCache()
17-
const client = new QueryClient({
18-
cache,
16+
const queryCache = new QueryCache()
17+
const queryClient = new QueryClient({
18+
queryCache,
1919
defaultOptions: {
2020
queries: {
2121
queryFn: defaultQueryFn,
@@ -25,7 +25,7 @@ const client = new QueryClient({
2525

2626
function App() {
2727
return (
28-
<QueryClientProvider client={client}>
28+
<QueryClientProvider client={queryClient}>
2929
<YourApp />
3030
</QueryClientProvider>
3131
)

docs/src/pages/guides/infinite-queries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function Projects() {
8888

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

91-
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.
91+
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.
9292

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

docs/src/pages/guides/initial-query-data.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,20 @@ function Todo({ todoId }) {
5555
const result = useQuery(['todo', todoId], () => fetch('/todos'), {
5656
initialData: () => {
5757
// Use a todo from the 'todos' query as the initial data for this todo query
58-
return client.getQueryData('todos')?.find(d => d.id === todoId)
58+
return queryClient.getQueryData('todos')?.find(d => d.id === todoId)
5959
},
6060
})
6161
}
6262
```
6363
64-
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:
64+
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:
6565
6666
```js
6767
function Todo({ todoId }) {
6868
const result = useQuery(['todo', todoId], () => fetch('/todos'), {
6969
initialData: () => {
7070
// Get the query state
71-
const state = client.getQueryState('todos')
71+
const state = queryClient.getQueryState('todos')
7272

7373
// If the query exists and has data that is no older than 10 seconds...
7474
if (state && Date.now() - state.updatedAt <= 10 * 1000) {

docs/src/pages/guides/invalidations-from-mutations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ When a successful `postTodo` mutation happens, we likely want all `todos` querie
1616
```js
1717
import { useMutation, useQueryClient } from 'react-query'
1818

19-
const client = useQueryClient()
19+
const queryClient = useQueryClient()
2020

2121
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
2222
const mutation = useMutation(addTodo, {
2323
onSuccess: () => {
24-
client.invalidateQueries('todos')
25-
client.invalidateQueries('reminders')
24+
queryClient.invalidateQueries('todos')
25+
queryClient.invalidateQueries('reminders')
2626
},
2727
})
2828
```

docs/src/pages/guides/migrating-to-react-query-3.md

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ This article explains how to migrate your application to React Query 3.
99

1010
### QueryClient
1111

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

1515
This has some benefits:
1616

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

28-
const cache = new QueryCache()
29-
const client = new QueryClient({ cache })
28+
// Create query cache
29+
const queryCache = new QueryCache()
30+
31+
// Create mutation cache (can be omitted to reduce file size when not using mutations)
32+
const mutationCache = new MutationCache()
33+
34+
// Create client
35+
const queryClient = new QueryClient({ queryCache, mutationCache })
3036

3137
function App() {
32-
return <QueryClientProvider client={client}>...</QueryClientProvider>
38+
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
3339
}
3440
```
3541

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

4450
function Todo() {
45-
const client = useQueryClient()
51+
const queryClient = useQueryClient()
4652

4753
const onClickButton = useCallback(() => {
48-
client.invalidateQueries('posts')
54+
queryClient.invalidateQueries('posts')
4955
}, [client])
5056

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

5965
```js
60-
const client = new QueryClient({
61-
cache,
66+
const queryClient = new QueryClient({
67+
queryCache,
6268
defaultOptions: {
6369
queries: {
6470
staleTime: Infinity,
@@ -157,6 +163,9 @@ mutate('todo', {
157163
onError: error => {
158164
console.error(error)
159165
},
166+
onSettled: () => {
167+
console.log('settled)
168+
},
160169
})
161170
```
162171
@@ -170,9 +179,14 @@ try {
170179
console.log(data)
171180
} catch (error) {
172181
console.error(error)
182+
} finally {
183+
console.log('settled)
173184
}
174185
```
175186
187+
Callbacks passed to the `mutate` or `mutateAsync` functions will now override the callbacks defined on `useMutation`.
188+
The `mutateAsync` function can be used to compose side effects.
189+
176190
### Query object syntax
177191
178192
The object syntax has been collapsed:
@@ -195,17 +209,17 @@ useQuery({
195209
196210
### queryCache.prefetchQuery()
197211
198-
The `client.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.
212+
The `queryClient.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.
199213
200-
Use the `client.fetchQueryData()` method to get the query data or error:
214+
Use the `queryClient.fetchQueryData()` method to get the query data or error:
201215
202216
```js
203217
// Prefetch a query:
204-
await client.prefetchQuery('posts', fetchPosts)
218+
await queryClient.prefetchQuery('posts', fetchPosts)
205219

206220
// Fetch a query:
207221
try {
208-
const data = await client.fetchQueryData('posts', fetchPosts)
222+
const data = await queryClient.fetchQueryData('posts', fetchPosts)
209223
} catch (error) {
210224
// Error handling
211225
}
@@ -237,7 +251,7 @@ The `queryCache.getQueries()` method has been replaced by `cache.findAll()`.
237251
238252
### queryCache.isFetching
239253
240-
The `queryCache.isFetching` property has been replaced by `client.isFetching()`.
254+
The `queryCache.isFetching` property has been replaced by `queryClient.isFetching()`.
241255
242256
### QueryOptions.enabled
243257
@@ -336,25 +350,25 @@ function Overview() {
336350
}
337351
```
338352
339-
#### client.watchQuery()
353+
#### QueryObserver
340354
341-
The `client.watchQuery()` method can be used to create and/or watch a query:
355+
A `QueryObserver` can be used to create and/or watch a query:
342356
343357
```js
344-
const observer = client.watchQuery('posts')
358+
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })
345359

346360
const unsubscribe = observer.subscribe(result => {
347361
console.log(result)
348362
unsubscribe()
349363
})
350364
```
351365
352-
#### client.watchQueries()
366+
#### QueriesObserver
353367
354-
The `client.watchQueries()` method can be used to create and/or watch multiple queries:
368+
A `QueriesObserver` can be used to create and/or watch multiple queries:
355369
356370
```js
357-
const observer = client.watchQueries([
371+
const observer = new QueriesObserver(queryClient, [
358372
{ queryKey: ['post', 1], queryFn: fetchPost },
359373
{ queryKey: ['post', 2], queryFn: fetchPost },
360374
])
@@ -365,18 +379,30 @@ const unsubscribe = observer.subscribe(result => {
365379
})
366380
```
367381
368-
## `client.setQueryDefaults`
382+
## `queryClient.setQueryDefaults`
369383
370-
The `client.setQueryDefaults()` method to set default options for a specific query. If the query does not exist yet it will create it.
384+
The `queryClient.setQueryDefaults()` method can be used to set default options for specific queries:
371385
372386
```js
373-
client.setQueryDefaults('posts', fetchPosts)
387+
queryClient.setQueryDefaults('posts', { queryFn: fetchPosts })
374388

375389
function Component() {
376390
const { data } = useQuery('posts')
377391
}
378392
```
379393
394+
## `queryClient.setMutationDefaults`
395+
396+
The `queryClient.setMutationDefaults()` method can be used to set default options for specific mutations:
397+
398+
```js
399+
queryClient.setMutationDefaults('addPost', { mutationFn: addPost })
400+
401+
function Component() {
402+
const { mutate } = useMutation('addPost')
403+
}
404+
```
405+
380406
#### useIsFetching()
381407
382408
The `useIsFetching()` hook now accepts filters which can be used to for example only show a spinner for certain type of queries:

docs/src/pages/guides/mutations.md

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Beyond those primary state, more information is available depending on the state
5151

5252
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**.
5353

54-
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.
54+
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.
5555

5656
> 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).
5757
@@ -120,18 +120,12 @@ useMutation(addTodo, {
120120
onMutate: variables => {
121121
// A mutation is about to happen!
122122

123-
// Optionally return a context object with a rollback function
124-
return {
125-
rollback: () => {
126-
// do some rollback logic
127-
},
128-
}
123+
// Optionally return a context containing data to use when for example rolling back
124+
return { id: 1 }
129125
},
130126
onError: (error, variables, context) => {
131127
// An error happened!
132-
if (context.rollback) {
133-
context.rollback()
134-
}
128+
console.log(`rolling back optimistic update with id ${context.id}`)
135129
},
136130
onSuccess: (data, variables, context) => {
137131
// Boom baby!
@@ -155,41 +149,37 @@ useMutation(addTodo, {
155149
})
156150
```
157151

158-
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:
159-
160-
- `onSuccess` - Will be fired after the `useMutation`-level `onSuccess` handler
161-
- `onError` - Will be fired after the `useMutation`-level `onError` handler
162-
- `onSettled` - Will be fired after the `useMutation`-level `onSettled` handler
152+
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`.
163153

164154
```js
165155
useMutation(addTodo, {
166156
onSuccess: (data, variables, context) => {
167-
// I will fire first
157+
// I will not fire
168158
},
169159
onError: (error, variables, context) => {
170-
// I will fire first
160+
// I will not fire
171161
},
172162
onSettled: (data, error, variables, context) => {
173-
// I will fire first
163+
// I will not fire
174164
},
175165
})
176166

177167
mutate(todo, {
178168
onSuccess: (data, variables, context) => {
179-
// I will fire second!
169+
// I will fire instead!
180170
},
181171
onError: (error, variables, context) => {
182-
// I will fire second!
172+
// I will fire instead!
183173
},
184174
onSettled: (data, error, variables, context) => {
185-
// I will fire second!
175+
// I will fire instead!
186176
},
187177
})
188178
```
189179

190180
## Promises
191181

192-
Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error:
182+
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.
193183

194184
```js
195185
const mutation = useMutation(addTodo)
@@ -199,5 +189,19 @@ try {
199189
console.log(todo)
200190
} catch (error) {
201191
console.error(error)
192+
} finally {
193+
console.log('done')
202194
}
203195
```
196+
197+
## Retry
198+
199+
By default React Query will not retry a mutation on error, but it is possible with the `retry` option:
200+
201+
```js
202+
const mutation = useMutation(addTodo, {
203+
retry: 3,
204+
})
205+
```
206+
207+
If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.

0 commit comments

Comments
 (0)