Skip to content

Commit b381489

Browse files
committed
feat: move mutations into the core
1 parent f4b2bdc commit b381489

File tree

83 files changed

+2694
-1453
lines changed

Some content is hidden

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

83 files changed

+2694
-1453
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: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ 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+
const queryCache = new QueryCache()
29+
const queryClient = new QueryClient({ queryCache })
3030

3131
function App() {
32-
return <QueryClientProvider client={client}>...</QueryClientProvider>
32+
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
3333
}
3434
```
3535

@@ -42,10 +42,10 @@ import { useCallback } from 'react'
4242
import { useQueryClient } from 'react-query'
4343

4444
function Todo() {
45-
const client = useQueryClient()
45+
const queryClient = useQueryClient()
4646

4747
const onClickButton = useCallback(() => {
48-
client.invalidateQueries('posts')
48+
queryClient.invalidateQueries('posts')
4949
}, [client])
5050

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

5959
```js
60-
const client = new QueryClient({
61-
cache,
60+
const queryClient = new QueryClient({
61+
queryCache,
6262
defaultOptions: {
6363
queries: {
6464
staleTime: Infinity,
@@ -157,6 +157,9 @@ mutate('todo', {
157157
onError: error => {
158158
console.error(error)
159159
},
160+
onSettled: () => {
161+
console.log('settled)
162+
},
160163
})
161164
```
162165
@@ -170,9 +173,14 @@ try {
170173
console.log(data)
171174
} catch (error) {
172175
console.error(error)
176+
} finally {
177+
console.log('settled)
173178
}
174179
```
175180
181+
Callbacks passed to the `mutate` or `mutateAsync` functions will now override the callbacks defined on `useMutation`.
182+
The `mutateAsync` function can be used to compose side effects.
183+
176184
### Query object syntax
177185
178186
The object syntax has been collapsed:
@@ -195,17 +203,17 @@ useQuery({
195203
196204
### queryCache.prefetchQuery()
197205
198-
The `client.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.
206+
The `queryClient.prefetchQuery()` method should now only be used for prefetching scenarios where the result is not relevant.
199207
200-
Use the `client.fetchQueryData()` method to get the query data or error:
208+
Use the `queryClient.fetchQueryData()` method to get the query data or error:
201209
202210
```js
203211
// Prefetch a query:
204-
await client.prefetchQuery('posts', fetchPosts)
212+
await queryClient.prefetchQuery('posts', fetchPosts)
205213

206214
// Fetch a query:
207215
try {
208-
const data = await client.fetchQueryData('posts', fetchPosts)
216+
const data = await queryClient.fetchQueryData('posts', fetchPosts)
209217
} catch (error) {
210218
// Error handling
211219
}
@@ -237,7 +245,7 @@ The `queryCache.getQueries()` method has been replaced by `cache.findAll()`.
237245
238246
### queryCache.isFetching
239247
240-
The `queryCache.isFetching` property has been replaced by `client.isFetching()`.
248+
The `queryCache.isFetching` property has been replaced by `queryClient.isFetching()`.
241249
242250
### QueryOptions.enabled
243251
@@ -336,25 +344,25 @@ function Overview() {
336344
}
337345
```
338346
339-
#### client.watchQuery()
347+
#### QueryObserver
340348
341-
The `client.watchQuery()` method can be used to create and/or watch a query:
349+
A `QueryObserver` can be used to create and/or watch a query:
342350
343351
```js
344-
const observer = client.watchQuery('posts')
352+
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })
345353

346354
const unsubscribe = observer.subscribe(result => {
347355
console.log(result)
348356
unsubscribe()
349357
})
350358
```
351359
352-
#### client.watchQueries()
360+
#### QueriesObserver
353361
354-
The `client.watchQueries()` method can be used to create and/or watch multiple queries:
362+
A `QueriesObserver` can be used to create and/or watch multiple queries:
355363
356364
```js
357-
const observer = client.watchQueries([
365+
const observer = new QueriesObserver(queryClient, [
358366
{ queryKey: ['post', 1], queryFn: fetchPost },
359367
{ queryKey: ['post', 2], queryFn: fetchPost },
360368
])
@@ -365,18 +373,30 @@ const unsubscribe = observer.subscribe(result => {
365373
})
366374
```
367375
368-
## `client.setQueryDefaults`
376+
## `queryClient.setQueryDefaults`
369377
370-
The `client.setQueryDefaults()` method to set default options for a specific query. If the query does not exist yet it will create it.
378+
The `queryClient.setQueryDefaults()` method can be used to set default options for specific queries:
371379
372380
```js
373-
client.setQueryDefaults('posts', fetchPosts)
381+
queryClient.setQueryDefaults('posts', { queryFn: fetchPosts })
374382

375383
function Component() {
376384
const { data } = useQuery('posts')
377385
}
378386
```
379387
388+
## `queryClient.setMutationDefaults`
389+
390+
The `queryClient.setMutationDefaults()` method can be used to set default options for specific mutations:
391+
392+
```js
393+
queryClient.setMutationDefaults('addPost', { mutationFn: addPost })
394+
395+
function Component() {
396+
const { mutate } = useMutation('addPost')
397+
}
398+
```
399+
380400
#### useIsFetching()
381401
382402
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)