Skip to content

Commit e6da990

Browse files
authored
feat(queryCache): add global onSuccess callback (TanStack#2404)
1 parent 475eb41 commit e6da990

File tree

8 files changed

+90
-12
lines changed

8 files changed

+90
-12
lines changed

docs/src/pages/reference/MutationCache.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const mutationCache = new MutationCache({
1414
onError: error => {
1515
console.log(error)
1616
},
17+
onSuccess: data => {
18+
console.log(data)
19+
}
1720
})
1821
```
1922

@@ -28,6 +31,14 @@ Its available methods are:
2831
- `onError?: (error: unknown, variables: unknown, context: unknown, mutation: Mutation) => void`
2932
- Optional
3033
- This function will be called if some mutation encounters an error.
34+
- `onSuccess?: (data: unknown, variables: unknown, context: unknown, mutation: Mutation) => void`
35+
- Optional
36+
- This function will be called if some mutation is successful.
37+
38+
## Global callbacks
39+
40+
The `onError` and `onSuccess` callbacks on the MutationCache can be used to handle these events on a global level. They are different to `defaultOptions` provided to the QueryClient because:
41+
- `defaultOptions` can be overridden by each Mutation - the global callbacks will **always** be called.
3142

3243
## `mutationCache.getAll`
3344

docs/src/pages/reference/QueryCache.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ id: QueryCache
33
title: QueryCache
44
---
55

6-
The `QueryCache` is the storage mechanism for React Query. It stores all of the data, meta information and state of queries it contains.
6+
The `QueryCache` is the storage mechanism for React Query. It stores all the data, meta information and state of queries it contains.
77

88
**Normally, you will not interact with the QueryCache directly and instead use the `QueryClient` for a specific cache.**
99

@@ -14,6 +14,9 @@ const queryCache = new QueryCache({
1414
onError: error => {
1515
console.log(error)
1616
},
17+
onSuccess: data => {
18+
console.log(data)
19+
}
1720
})
1821

1922
const query = queryCache.find('posts')
@@ -31,6 +34,15 @@ Its available methods are:
3134
- `onError?: (error: unknown, query: Query) => void`
3235
- Optional
3336
- This function will be called if some query encounters an error.
37+
- `onSuccess?: (data: unknown, query: Query) => void`
38+
- Optional
39+
- This function will be called if some query is successful.
40+
41+
## Global callbacks
42+
43+
The `onError` and `onSuccess` callbacks on the QueryCache can be used to handle these events on a global level. They are different to `defaultOptions` provided to the QueryClient because:
44+
- `defaultOptions` can be overridden by each Query - the global callbacks will **always** be called.
45+
- `defaultOptions` callbacks will be called once for each Observer, while the global callbacks will only be called once per Query.
3446

3547
## `queryCache.find`
3648

src/core/mutation.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ export class Mutation<
156156
.then(() => this.executeMutation())
157157
.then(result => {
158158
data = result
159+
// Notify cache callback
160+
this.mutationCache.config.onSuccess?.(
161+
data,
162+
this.state.variables,
163+
this.state.context,
164+
this as Mutation<unknown, unknown, unknown, unknown>
165+
)
159166
})
160167
.then(() =>
161168
this.options.onSuccess?.(
@@ -178,14 +185,12 @@ export class Mutation<
178185
})
179186
.catch(error => {
180187
// Notify cache callback
181-
if (this.mutationCache.config.onError) {
182-
this.mutationCache.config.onError(
183-
error,
184-
this.state.variables,
185-
this.state.context,
186-
this as Mutation<unknown, unknown, unknown, unknown>
187-
)
188-
}
188+
this.mutationCache.config.onError?.(
189+
error,
190+
this.state.variables,
191+
this.state.context,
192+
this as Mutation<unknown, unknown, unknown, unknown>
193+
)
189194

190195
// Log error
191196
getLogger().error(error)

src/core/mutationCache.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ interface MutationCacheConfig {
1414
context: unknown,
1515
mutation: Mutation<unknown, unknown, unknown, unknown>
1616
) => void
17+
onSuccess?: (
18+
data: unknown,
19+
variables: unknown,
20+
context: unknown,
21+
mutation: Mutation<unknown, unknown, unknown, unknown>
22+
) => void
1723
}
1824

1925
type MutationCacheListener = (mutation?: Mutation) => void

src/core/query.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ export class Query<
426426
onSuccess: data => {
427427
this.setData(data as TData)
428428

429+
// Notify cache callback
430+
this.cache.config.onSuccess?.(data, this as Query<any, any, any, any>)
431+
429432
// Remove query after fetching if cache time is 0
430433
if (this.cacheTime === 0) {
431434
this.optionalRemove()
@@ -442,9 +445,7 @@ export class Query<
442445

443446
if (!isCancelledError(error)) {
444447
// Notify cache callback
445-
if (this.cache.config.onError) {
446-
this.cache.config.onError(error, this as Query<any, any, any, any>)
447-
}
448+
this.cache.config.onError?.(error, this as Query<any, any, any, any>)
448449

449450
// Log error
450451
getLogger().error(error)

src/core/queryCache.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { QueryObserver } from './queryObserver'
1515

1616
interface QueryCacheConfig {
1717
onError?: (error: unknown, query: Query<unknown, unknown, unknown>) => void
18+
onSuccess?: (data: unknown, query: Query<unknown, unknown, unknown>) => void
1819
}
1920

2021
interface QueryHashMap {

src/core/tests/mutationCache.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,34 @@ describe('mutationCache', () => {
2525
expect(onError).toHaveBeenCalledWith('error', 'vars', 'context', mutation)
2626
})
2727
})
28+
describe('MutationCacheConfig.onSuccess', () => {
29+
test('should be called when a mutation is successful', async () => {
30+
const consoleMock = mockConsoleError()
31+
const key = queryKey()
32+
const onSuccess = jest.fn()
33+
const testCache = new MutationCache({ onSuccess })
34+
const testClient = new QueryClient({ mutationCache: testCache })
35+
36+
try {
37+
await testClient.executeMutation({
38+
mutationKey: key,
39+
variables: 'vars',
40+
mutationFn: () => Promise.resolve({ data: 5 }),
41+
onMutate: () => 'context',
42+
})
43+
} catch {
44+
consoleMock.mockRestore()
45+
}
46+
47+
const mutation = testCache.getAll()[0]
48+
expect(onSuccess).toHaveBeenCalledWith(
49+
{ data: 5 },
50+
'vars',
51+
'context',
52+
mutation
53+
)
54+
})
55+
})
2856

2957
describe('find', () => {
3058
test('should filter correctly', async () => {

src/core/tests/queryCache.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,18 @@ describe('queryCache', () => {
144144
expect(onError).toHaveBeenCalledWith('error', query)
145145
})
146146
})
147+
148+
describe('QueryCacheConfig.onSuccess', () => {
149+
test('should be called when a query is successful', async () => {
150+
const consoleMock = mockConsoleError()
151+
const key = queryKey()
152+
const onSuccess = jest.fn()
153+
const testCache = new QueryCache({ onSuccess })
154+
const testClient = new QueryClient({ queryCache: testCache })
155+
await testClient.prefetchQuery(key, () => Promise.resolve({ data: 5 }))
156+
consoleMock.mockRestore()
157+
const query = testCache.find(key)
158+
expect(onSuccess).toHaveBeenCalledWith({ data: 5 }, query)
159+
})
160+
})
147161
})

0 commit comments

Comments
 (0)