@tanstack/react-query 4.42.2 → 4.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/lib/__tests__/mutationOptions.test.d.ts +2 -0
- package/build/lib/__tests__/mutationOptions.test.d.ts.map +1 -0
- package/build/lib/__tests__/mutationOptions.types.test.d.ts +2 -0
- package/build/lib/__tests__/mutationOptions.types.test.d.ts.map +1 -0
- package/build/lib/__tests__/usePrefetchInfiniteQuery.types.test.d.ts +2 -0
- package/build/lib/__tests__/usePrefetchInfiniteQuery.types.test.d.ts.map +1 -0
- package/build/lib/__tests__/usePrefetchQuery.types.test.d.ts +2 -0
- package/build/lib/__tests__/usePrefetchQuery.types.test.d.ts.map +1 -0
- package/build/lib/index.d.ts +3 -0
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.esm.js +3 -0
- package/build/lib/index.esm.js.map +1 -1
- package/build/lib/index.js +6 -0
- package/build/lib/index.js.map +1 -1
- package/build/lib/index.mjs +3 -0
- package/build/lib/index.mjs.map +1 -1
- package/build/lib/mutationOptions.d.ts +5 -0
- package/build/lib/mutationOptions.d.ts.map +1 -0
- package/build/lib/mutationOptions.esm.js +6 -0
- package/build/lib/mutationOptions.esm.js.map +1 -0
- package/build/lib/mutationOptions.js +10 -0
- package/build/lib/mutationOptions.js.map +1 -0
- package/build/lib/mutationOptions.mjs +6 -0
- package/build/lib/mutationOptions.mjs.map +1 -0
- package/build/lib/usePrefetchInfiniteQuery.d.ts +3 -0
- package/build/lib/usePrefetchInfiniteQuery.d.ts.map +1 -0
- package/build/lib/usePrefetchInfiniteQuery.esm.js +12 -0
- package/build/lib/usePrefetchInfiniteQuery.esm.js.map +1 -0
- package/build/lib/usePrefetchInfiniteQuery.js +16 -0
- package/build/lib/usePrefetchInfiniteQuery.js.map +1 -0
- package/build/lib/usePrefetchInfiniteQuery.mjs +12 -0
- package/build/lib/usePrefetchInfiniteQuery.mjs.map +1 -0
- package/build/lib/usePrefetchQuery.d.ts +3 -0
- package/build/lib/usePrefetchQuery.d.ts.map +1 -0
- package/build/lib/usePrefetchQuery.esm.js +12 -0
- package/build/lib/usePrefetchQuery.esm.js.map +1 -0
- package/build/lib/usePrefetchQuery.js +16 -0
- package/build/lib/usePrefetchQuery.js.map +1 -0
- package/build/lib/usePrefetchQuery.mjs +12 -0
- package/build/lib/usePrefetchQuery.mjs.map +1 -0
- package/build/umd/index.development.js +23 -0
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/mutationOptions.test.tsx +140 -0
- package/src/__tests__/mutationOptions.types.test.tsx +438 -0
- package/src/__tests__/usePrefetchInfiniteQuery.types.test.tsx +45 -0
- package/src/__tests__/usePrefetchQuery.types.test.tsx +56 -0
- package/src/index.ts +3 -0
- package/src/mutationOptions.ts +34 -0
- package/src/usePrefetchInfiniteQuery.ts +23 -0
- package/src/usePrefetchQuery.ts +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.44.0",
|
|
4
4
|
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"use-sync-external-store": "^1.6.0",
|
|
56
|
-
"@tanstack/query-core": "4.
|
|
56
|
+
"@tanstack/query-core": "4.44.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { QueryClient } from '@tanstack/query-core'
|
|
3
|
+
import { fireEvent, waitFor } from '@testing-library/react'
|
|
4
|
+
import { mutationOptions } from '../mutationOptions'
|
|
5
|
+
import { useIsMutating, useMutation } from '..'
|
|
6
|
+
import { renderWithClient, sleep } from './utils'
|
|
7
|
+
import type { UseMutationOptions } from '../types'
|
|
8
|
+
|
|
9
|
+
describe('mutationOptions', () => {
|
|
10
|
+
it('should return the object received as a parameter without any modification (with mutationKey)', () => {
|
|
11
|
+
const object: UseMutationOptions = {
|
|
12
|
+
mutationKey: ['key'],
|
|
13
|
+
mutationFn: () => Promise.resolve(5),
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
expect(mutationOptions(object)).toBe(object)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should return the object received as a parameter without any modification (without mutationKey)', () => {
|
|
20
|
+
const object: UseMutationOptions = {
|
|
21
|
+
mutationFn: () => Promise.resolve(5),
|
|
22
|
+
} as const
|
|
23
|
+
|
|
24
|
+
expect(mutationOptions(object)).toBe(object)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should work with useMutation (with mutationKey)', async () => {
|
|
28
|
+
const queryClient = new QueryClient()
|
|
29
|
+
const mutationOpts = mutationOptions({
|
|
30
|
+
mutationKey: ['key'],
|
|
31
|
+
mutationFn: () => sleep(10).then(() => 'data'),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function Page() {
|
|
35
|
+
const mutation = useMutation(mutationOpts)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
<button onClick={() => mutation.mutate()}>mutate</button>
|
|
40
|
+
<span>{mutation.data ?? 'empty'}</span>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
46
|
+
|
|
47
|
+
expect(rendered.getByText('empty')).toBeTruthy()
|
|
48
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
49
|
+
await waitFor(() => rendered.getByText('data'))
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should work with useMutation (without mutationKey)', async () => {
|
|
53
|
+
const queryClient = new QueryClient()
|
|
54
|
+
const mutationOpts = mutationOptions({
|
|
55
|
+
mutationFn: () => sleep(10).then(() => 'data'),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
function Page() {
|
|
59
|
+
const mutation = useMutation(mutationOpts)
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
<button onClick={() => mutation.mutate()}>mutate</button>
|
|
64
|
+
<span>{mutation.data ?? 'empty'}</span>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
70
|
+
|
|
71
|
+
expect(rendered.getByText('empty')).toBeTruthy()
|
|
72
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
73
|
+
await waitFor(() => rendered.getByText('data'))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should work with useIsMutating filtering by mutationKey', async () => {
|
|
77
|
+
const queryClient = new QueryClient()
|
|
78
|
+
const mutationOpts1 = mutationOptions({
|
|
79
|
+
mutationKey: ['key1'],
|
|
80
|
+
mutationFn: () => sleep(50).then(() => 'data1'),
|
|
81
|
+
})
|
|
82
|
+
const mutationOpts2 = mutationOptions({
|
|
83
|
+
mutationKey: ['key2'],
|
|
84
|
+
mutationFn: () => sleep(50).then(() => 'data2'),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
function Page() {
|
|
88
|
+
const isMutating = useIsMutating({
|
|
89
|
+
mutationKey: mutationOpts1.mutationKey,
|
|
90
|
+
})
|
|
91
|
+
const { mutate: mutate1 } = useMutation(mutationOpts1)
|
|
92
|
+
const { mutate: mutate2 } = useMutation(mutationOpts2)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
<span>isMutating: {isMutating}</span>
|
|
97
|
+
<button onClick={() => mutate1()}>mutate1</button>
|
|
98
|
+
<button onClick={() => mutate2()}>mutate2</button>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
104
|
+
|
|
105
|
+
rendered.getByText('isMutating: 0')
|
|
106
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
|
|
107
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
|
|
108
|
+
await waitFor(() => rendered.getByText('isMutating: 1'))
|
|
109
|
+
await waitFor(() => rendered.getByText('isMutating: 0'))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should work with queryClient.isMutating', async () => {
|
|
113
|
+
const queryClient = new QueryClient()
|
|
114
|
+
const mutationOpts = mutationOptions({
|
|
115
|
+
mutationKey: ['mutation'],
|
|
116
|
+
mutationFn: () => sleep(10).then(() => 'data'),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
function Page() {
|
|
120
|
+
const isMutating = queryClient.isMutating({
|
|
121
|
+
mutationKey: mutationOpts.mutationKey,
|
|
122
|
+
})
|
|
123
|
+
const { mutate } = useMutation(mutationOpts)
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div>
|
|
127
|
+
<span>isMutating: {isMutating}</span>
|
|
128
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
134
|
+
|
|
135
|
+
rendered.getByText('isMutating: 0')
|
|
136
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
137
|
+
await waitFor(() => rendered.getByText('isMutating: 1'))
|
|
138
|
+
await waitFor(() => rendered.getByText('isMutating: 0'))
|
|
139
|
+
})
|
|
140
|
+
})
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { expectTypeOf } from 'expect-type'
|
|
2
|
+
import { QueryClient } from '@tanstack/query-core'
|
|
3
|
+
import {
|
|
4
|
+
type UseMutationOptions,
|
|
5
|
+
type UseMutationResult,
|
|
6
|
+
mutationOptions,
|
|
7
|
+
useIsMutating,
|
|
8
|
+
useMutation,
|
|
9
|
+
useQueryClient,
|
|
10
|
+
} from '..'
|
|
11
|
+
import { doNotExecute } from './utils'
|
|
12
|
+
import type { MutationKey, OmitKeyof, WithRequired } from '@tanstack/query-core'
|
|
13
|
+
|
|
14
|
+
const mutationKey = ['key'] as const
|
|
15
|
+
const mutationFn = (_input: { id: string }) =>
|
|
16
|
+
Promise.resolve({ field: 'success' })
|
|
17
|
+
|
|
18
|
+
describe('mutationOptions', () => {
|
|
19
|
+
it('should not allow excess properties', () => {
|
|
20
|
+
doNotExecute(() => {
|
|
21
|
+
// @ts-expect-error this is a good error, because onMutates does not exist!
|
|
22
|
+
mutationOptions({
|
|
23
|
+
mutationFn: () => Promise.resolve(5),
|
|
24
|
+
mutationKey: ['key'],
|
|
25
|
+
onMutates: 1000,
|
|
26
|
+
onSuccess: (data) => {
|
|
27
|
+
expectTypeOf(data).toEqualTypeOf<number>()
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should infer types for callbacks', () => {
|
|
34
|
+
doNotExecute(() => {
|
|
35
|
+
mutationOptions({
|
|
36
|
+
mutationFn: () => Promise.resolve(5),
|
|
37
|
+
mutationKey: ['key'],
|
|
38
|
+
onSuccess: (data) => {
|
|
39
|
+
expectTypeOf(data).toEqualTypeOf<number>()
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should infer types for onError callback', () => {
|
|
46
|
+
doNotExecute(() => {
|
|
47
|
+
mutationOptions({
|
|
48
|
+
mutationFn: () => {
|
|
49
|
+
throw new Error('fail')
|
|
50
|
+
},
|
|
51
|
+
mutationKey: ['key'],
|
|
52
|
+
onError: (error) => {
|
|
53
|
+
expectTypeOf(error).toEqualTypeOf<unknown>()
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should infer types for variables', () => {
|
|
60
|
+
doNotExecute(() => {
|
|
61
|
+
mutationOptions<number, unknown, { id: string }>({
|
|
62
|
+
mutationFn: (vars) => {
|
|
63
|
+
expectTypeOf(vars).toEqualTypeOf<{ id: string }>()
|
|
64
|
+
return Promise.resolve(5)
|
|
65
|
+
},
|
|
66
|
+
mutationKey: ['with-vars'],
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should infer context type correctly', () => {
|
|
72
|
+
doNotExecute(() => {
|
|
73
|
+
mutationOptions<number, unknown, void, { name: string }>({
|
|
74
|
+
mutationFn: () => Promise.resolve(5),
|
|
75
|
+
mutationKey: ['key'],
|
|
76
|
+
onMutate: () => {
|
|
77
|
+
return { name: 'context' }
|
|
78
|
+
},
|
|
79
|
+
onSuccess: (_data, _variables, context) => {
|
|
80
|
+
expectTypeOf(context).toEqualTypeOf<{ name: string } | undefined>()
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should error if mutationFn return type mismatches TData', () => {
|
|
87
|
+
doNotExecute(() => {
|
|
88
|
+
mutationOptions<number>({
|
|
89
|
+
// @ts-expect-error this is a good error, because return type is string, not number
|
|
90
|
+
mutationFn: async () => Promise.resolve('wrong return'),
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should allow mutationKey to be omitted', () => {
|
|
96
|
+
doNotExecute(() => {
|
|
97
|
+
mutationOptions({
|
|
98
|
+
mutationFn: () => Promise.resolve(123),
|
|
99
|
+
onSuccess: (data) => {
|
|
100
|
+
expectTypeOf(data).toEqualTypeOf<number>()
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should infer all types when not explicitly provided', () => {
|
|
107
|
+
doNotExecute(() => {
|
|
108
|
+
expectTypeOf(
|
|
109
|
+
mutationOptions({
|
|
110
|
+
mutationFn: (id: string) => Promise.resolve(id.length),
|
|
111
|
+
mutationKey: ['key'],
|
|
112
|
+
onSuccess: (data) => {
|
|
113
|
+
expectTypeOf(data).toEqualTypeOf<number>()
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
).toEqualTypeOf<
|
|
117
|
+
WithRequired<
|
|
118
|
+
UseMutationOptions<number, unknown, string, unknown>,
|
|
119
|
+
'mutationKey'
|
|
120
|
+
>
|
|
121
|
+
>()
|
|
122
|
+
expectTypeOf(
|
|
123
|
+
mutationOptions({
|
|
124
|
+
mutationFn: (id: string) => Promise.resolve(id.length),
|
|
125
|
+
onSuccess: (data) => {
|
|
126
|
+
expectTypeOf(data).toEqualTypeOf<number>()
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
).toEqualTypeOf<
|
|
130
|
+
OmitKeyof<
|
|
131
|
+
UseMutationOptions<number, unknown, string, unknown>,
|
|
132
|
+
'mutationKey'
|
|
133
|
+
>
|
|
134
|
+
>()
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should infer types when used with useMutation', () => {
|
|
139
|
+
doNotExecute(() => {
|
|
140
|
+
const mutation = useMutation(
|
|
141
|
+
mutationOptions({
|
|
142
|
+
mutationKey: ['key'],
|
|
143
|
+
mutationFn: () => Promise.resolve('data'),
|
|
144
|
+
onSuccess: (data) => {
|
|
145
|
+
expectTypeOf(data).toEqualTypeOf<string>()
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
expectTypeOf(mutation).toEqualTypeOf<
|
|
150
|
+
UseMutationResult<string, unknown, void, unknown>
|
|
151
|
+
>()
|
|
152
|
+
|
|
153
|
+
// should allow when used with useMutation without mutationKey
|
|
154
|
+
useMutation(
|
|
155
|
+
mutationOptions({
|
|
156
|
+
mutationFn: () => Promise.resolve('data'),
|
|
157
|
+
onSuccess: (data) => {
|
|
158
|
+
expectTypeOf(data).toEqualTypeOf<string>()
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should be used with useMutation and spread with additional options', () => {
|
|
166
|
+
doNotExecute(() => {
|
|
167
|
+
const result = useMutation({
|
|
168
|
+
...mutationOptions({
|
|
169
|
+
mutationKey,
|
|
170
|
+
mutationFn,
|
|
171
|
+
}),
|
|
172
|
+
retry: 3,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
expectTypeOf(result).toEqualTypeOf<
|
|
176
|
+
UseMutationResult<{ field: string }, unknown, { id: string }, unknown>
|
|
177
|
+
>()
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should preserve mutationKey for use with useIsMutating/queryClient', () => {
|
|
182
|
+
doNotExecute(() => {
|
|
183
|
+
const options = mutationOptions({
|
|
184
|
+
mutationKey: ['todos', 'create'] as const,
|
|
185
|
+
mutationFn: (input: { title: string }) =>
|
|
186
|
+
Promise.resolve({ id: 1, title: input.title }),
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// mutationKey is MutationKey, usable with filters
|
|
190
|
+
expectTypeOf(options.mutationKey).toMatchTypeOf<MutationKey>()
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should work with void variables (no arguments to mutationFn)', () => {
|
|
195
|
+
doNotExecute(() => {
|
|
196
|
+
const options = mutationOptions({
|
|
197
|
+
mutationKey,
|
|
198
|
+
mutationFn: () => Promise.resolve('done'),
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const result = useMutation(options)
|
|
202
|
+
|
|
203
|
+
// mutate should be callable without arguments
|
|
204
|
+
result.mutate()
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('should infer TContext from onMutate when explicitly typed', () => {
|
|
209
|
+
doNotExecute(() => {
|
|
210
|
+
mutationOptions<
|
|
211
|
+
{ success: boolean },
|
|
212
|
+
unknown,
|
|
213
|
+
string,
|
|
214
|
+
{ previousData: string }
|
|
215
|
+
>({
|
|
216
|
+
mutationKey,
|
|
217
|
+
mutationFn: (_id: string) => Promise.resolve({ success: true }),
|
|
218
|
+
onMutate: (variables) => {
|
|
219
|
+
expectTypeOf(variables).toEqualTypeOf<string>()
|
|
220
|
+
return { previousData: 'backup' }
|
|
221
|
+
},
|
|
222
|
+
onError: (_error, variables, context) => {
|
|
223
|
+
expectTypeOf(variables).toEqualTypeOf<string>()
|
|
224
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
225
|
+
{ previousData: string } | undefined
|
|
226
|
+
>()
|
|
227
|
+
},
|
|
228
|
+
onSuccess: (data, variables, context) => {
|
|
229
|
+
expectTypeOf(data).toEqualTypeOf<{ success: boolean }>()
|
|
230
|
+
expectTypeOf(variables).toEqualTypeOf<string>()
|
|
231
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
232
|
+
{ previousData: string } | undefined
|
|
233
|
+
>()
|
|
234
|
+
},
|
|
235
|
+
onSettled: (data, _error, variables, context) => {
|
|
236
|
+
expectTypeOf(data).toEqualTypeOf<{ success: boolean } | undefined>()
|
|
237
|
+
expectTypeOf(variables).toEqualTypeOf<string>()
|
|
238
|
+
expectTypeOf(context).toEqualTypeOf<
|
|
239
|
+
{ previousData: string } | undefined
|
|
240
|
+
>()
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should work with complex generic types', () => {
|
|
247
|
+
doNotExecute(() => {
|
|
248
|
+
interface CreateUserInput {
|
|
249
|
+
name: string
|
|
250
|
+
email: string
|
|
251
|
+
roles: Array<'admin' | 'user'>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface User {
|
|
255
|
+
id: number
|
|
256
|
+
name: string
|
|
257
|
+
email: string
|
|
258
|
+
roles: Array<'admin' | 'user'>
|
|
259
|
+
createdAt: Date
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
interface OptimisticContext {
|
|
263
|
+
previousUsers: Array<User>
|
|
264
|
+
tempId: number
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const options = mutationOptions<
|
|
268
|
+
User,
|
|
269
|
+
unknown,
|
|
270
|
+
CreateUserInput,
|
|
271
|
+
OptimisticContext
|
|
272
|
+
>({
|
|
273
|
+
mutationKey: ['users', 'create'] as const,
|
|
274
|
+
mutationFn: (input: CreateUserInput) =>
|
|
275
|
+
Promise.resolve({
|
|
276
|
+
id: 1,
|
|
277
|
+
...input,
|
|
278
|
+
createdAt: new Date(),
|
|
279
|
+
} as User),
|
|
280
|
+
onMutate: (variables) => {
|
|
281
|
+
expectTypeOf(variables).toEqualTypeOf<CreateUserInput>()
|
|
282
|
+
return { previousUsers: [], tempId: Date.now() }
|
|
283
|
+
},
|
|
284
|
+
onError: (_error, _variables, context) => {
|
|
285
|
+
expectTypeOf(context).toEqualTypeOf<OptimisticContext | undefined>()
|
|
286
|
+
},
|
|
287
|
+
onSuccess: (data, variables, context) => {
|
|
288
|
+
expectTypeOf(data).toEqualTypeOf<User>()
|
|
289
|
+
expectTypeOf(variables).toEqualTypeOf<CreateUserInput>()
|
|
290
|
+
expectTypeOf(context).toEqualTypeOf<OptimisticContext | undefined>()
|
|
291
|
+
},
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const result = useMutation(options)
|
|
295
|
+
expectTypeOf(result.data).toEqualTypeOf<User | undefined>()
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should be usable in a factory pattern', () => {
|
|
300
|
+
doNotExecute(() => {
|
|
301
|
+
const mutations = {
|
|
302
|
+
create: () =>
|
|
303
|
+
mutationOptions({
|
|
304
|
+
mutationKey: ['items', 'create'] as const,
|
|
305
|
+
mutationFn: (input: { name: string }) =>
|
|
306
|
+
Promise.resolve({ id: 1, name: input.name }),
|
|
307
|
+
}),
|
|
308
|
+
delete: () =>
|
|
309
|
+
mutationOptions({
|
|
310
|
+
mutationKey: ['items', 'delete'] as const,
|
|
311
|
+
mutationFn: (_id: number) => Promise.resolve(undefined),
|
|
312
|
+
}),
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const createResult = useMutation(mutations.create())
|
|
316
|
+
expectTypeOf(createResult.data).toEqualTypeOf<
|
|
317
|
+
{ id: number; name: string } | undefined
|
|
318
|
+
>()
|
|
319
|
+
|
|
320
|
+
const deleteResult = useMutation(mutations.delete())
|
|
321
|
+
expectTypeOf(deleteResult.data).toEqualTypeOf<undefined>()
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('should work with queryClient mutation cache filters', () => {
|
|
326
|
+
doNotExecute(async () => {
|
|
327
|
+
const queryClient = useQueryClient()
|
|
328
|
+
const options = mutationOptions({
|
|
329
|
+
mutationKey: ['key'] as const,
|
|
330
|
+
mutationFn: () => Promise.resolve('data'),
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
queryClient.getMutationCache().findAll({
|
|
334
|
+
mutationKey: options.mutationKey,
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should infer types when used with queryClient.isMutating', () => {
|
|
340
|
+
doNotExecute(() => {
|
|
341
|
+
const queryClient = new QueryClient()
|
|
342
|
+
|
|
343
|
+
const isMutating = queryClient.isMutating({
|
|
344
|
+
mutationKey: mutationOptions({
|
|
345
|
+
mutationKey: ['key'],
|
|
346
|
+
mutationFn: () => Promise.resolve(5),
|
|
347
|
+
}).mutationKey,
|
|
348
|
+
})
|
|
349
|
+
expectTypeOf(isMutating).toEqualTypeOf<number>()
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('should handle union type variables', () => {
|
|
354
|
+
doNotExecute(() => {
|
|
355
|
+
type Action =
|
|
356
|
+
| { type: 'create'; payload: { name: string } }
|
|
357
|
+
| { type: 'delete'; payload: { id: number } }
|
|
358
|
+
|
|
359
|
+
const options = mutationOptions({
|
|
360
|
+
mutationKey,
|
|
361
|
+
mutationFn: (_action: Action) => Promise.resolve('done'),
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const result = useMutation(options)
|
|
365
|
+
result.mutate({ type: 'create', payload: { name: 'test' } })
|
|
366
|
+
result.mutate({ type: 'delete', payload: { id: 1 } })
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('should properly narrow mutationKey presence based on overload', () => {
|
|
371
|
+
doNotExecute(() => {
|
|
372
|
+
// With mutationKey: mutationKey is required in the return type
|
|
373
|
+
const withKey = mutationOptions({
|
|
374
|
+
mutationKey: ['key'] as const,
|
|
375
|
+
mutationFn: () => Promise.resolve(1),
|
|
376
|
+
})
|
|
377
|
+
expectTypeOf(withKey.mutationKey).toMatchTypeOf<MutationKey>()
|
|
378
|
+
|
|
379
|
+
// Without mutationKey: mutationKey should not be accessible
|
|
380
|
+
const withoutKey = mutationOptions({
|
|
381
|
+
mutationFn: () => Promise.resolve(1),
|
|
382
|
+
})
|
|
383
|
+
// @ts-expect-error mutationKey should not exist
|
|
384
|
+
withoutKey.mutationKey
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('should allow mutationKey to be used as MutationKey', () => {
|
|
389
|
+
doNotExecute(() => {
|
|
390
|
+
const options = mutationOptions({
|
|
391
|
+
mutationKey: ['todos', { status: 'active' }] as const,
|
|
392
|
+
mutationFn: () => Promise.resolve(true),
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const key: MutationKey = options.mutationKey
|
|
396
|
+
expectTypeOf(key).toMatchTypeOf<MutationKey>()
|
|
397
|
+
})
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('should infer types when used with useIsMutating via mutationKey filter', () => {
|
|
401
|
+
doNotExecute(() => {
|
|
402
|
+
const options = mutationOptions({
|
|
403
|
+
mutationKey: ['key'] as const,
|
|
404
|
+
mutationFn: () => Promise.resolve(5),
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
// mutationKey from mutationOptions can be used in MutationFilters
|
|
408
|
+
const isMutating = useIsMutating({
|
|
409
|
+
mutationKey: options.mutationKey,
|
|
410
|
+
})
|
|
411
|
+
expectTypeOf(isMutating).toEqualTypeOf<number>()
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should infer types when used with useIsMutating passing mutationKey directly', () => {
|
|
416
|
+
doNotExecute(() => {
|
|
417
|
+
const options = mutationOptions({
|
|
418
|
+
mutationKey: ['key'] as const,
|
|
419
|
+
mutationFn: () => Promise.resolve(5),
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
// v4 useIsMutating accepts MutationKey as first arg
|
|
423
|
+
const isMutating = useIsMutating(options.mutationKey)
|
|
424
|
+
expectTypeOf(isMutating).toEqualTypeOf<number>()
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should not allow passing mutationOptions without mutationKey to useIsMutating filter', () => {
|
|
429
|
+
doNotExecute(() => {
|
|
430
|
+
const options = mutationOptions({
|
|
431
|
+
mutationFn: () => Promise.resolve(5),
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// @ts-expect-error mutationKey does not exist on options without mutationKey
|
|
435
|
+
useIsMutating({ mutationKey: options.mutationKey })
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expectTypeOf } from 'expect-type'
|
|
2
|
+
import { usePrefetchInfiniteQuery } from '../usePrefetchInfiniteQuery'
|
|
3
|
+
import { doNotExecute } from './utils'
|
|
4
|
+
|
|
5
|
+
describe('usePrefetchInfiniteQuery', () => {
|
|
6
|
+
it('should return nothing', () => {
|
|
7
|
+
doNotExecute(() => {
|
|
8
|
+
const result = usePrefetchInfiniteQuery({
|
|
9
|
+
queryKey: ['key'],
|
|
10
|
+
queryFn: () => Promise.resolve(5),
|
|
11
|
+
getNextPageParam: () => 1,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expectTypeOf(result).toEqualTypeOf<void>()
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should not allow refetchInterval, enabled or throwOnError options', () => {
|
|
19
|
+
doNotExecute(() => {
|
|
20
|
+
usePrefetchInfiniteQuery({
|
|
21
|
+
queryKey: ['key'],
|
|
22
|
+
queryFn: () => Promise.resolve(5),
|
|
23
|
+
getNextPageParam: () => 1,
|
|
24
|
+
// @ts-expect-error TS2353
|
|
25
|
+
refetchInterval: 1000,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
usePrefetchInfiniteQuery({
|
|
29
|
+
queryKey: ['key'],
|
|
30
|
+
queryFn: () => Promise.resolve(5),
|
|
31
|
+
getNextPageParam: () => 1,
|
|
32
|
+
// @ts-expect-error TS2353
|
|
33
|
+
enabled: true,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
usePrefetchInfiniteQuery({
|
|
37
|
+
queryKey: ['key'],
|
|
38
|
+
queryFn: () => Promise.resolve(5),
|
|
39
|
+
getNextPageParam: () => 1,
|
|
40
|
+
// @ts-expect-error TS2353
|
|
41
|
+
throwOnError: true,
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { expectTypeOf } from 'expect-type'
|
|
2
|
+
import { usePrefetchQuery } from '..'
|
|
3
|
+
import { doNotExecute } from './utils'
|
|
4
|
+
|
|
5
|
+
describe('usePrefetchQuery', () => {
|
|
6
|
+
it('should return nothing', () => {
|
|
7
|
+
doNotExecute(() => {
|
|
8
|
+
const result = usePrefetchQuery({
|
|
9
|
+
queryKey: ['key'],
|
|
10
|
+
queryFn: () => Promise.resolve(5),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
expectTypeOf(result).toEqualTypeOf<void>()
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should not allow refetchInterval, enabled or throwOnError options', () => {
|
|
18
|
+
doNotExecute(() => {
|
|
19
|
+
usePrefetchQuery({
|
|
20
|
+
queryKey: ['key'],
|
|
21
|
+
queryFn: () => Promise.resolve(5),
|
|
22
|
+
// @ts-expect-error TS2345
|
|
23
|
+
refetchInterval: 1000,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
usePrefetchQuery({
|
|
27
|
+
queryKey: ['key'],
|
|
28
|
+
queryFn: () => Promise.resolve(5),
|
|
29
|
+
// @ts-expect-error TS2345
|
|
30
|
+
enabled: true,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
usePrefetchQuery({
|
|
34
|
+
queryKey: ['key'],
|
|
35
|
+
queryFn: () => Promise.resolve(5),
|
|
36
|
+
// @ts-expect-error TS2345
|
|
37
|
+
throwOnError: true,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should not allow skipToken in queryFn', () => {
|
|
43
|
+
doNotExecute(() => {
|
|
44
|
+
usePrefetchQuery({
|
|
45
|
+
queryKey: ['key'],
|
|
46
|
+
// @ts-expect-error
|
|
47
|
+
queryFn: skipToken,
|
|
48
|
+
})
|
|
49
|
+
usePrefetchQuery({
|
|
50
|
+
queryKey: ['key'],
|
|
51
|
+
// @ts-expect-error
|
|
52
|
+
queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5),
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -43,6 +43,9 @@ export {
|
|
|
43
43
|
} from './QueryErrorResetBoundary'
|
|
44
44
|
export { useIsFetching } from './useIsFetching'
|
|
45
45
|
export { useIsMutating } from './useIsMutating'
|
|
46
|
+
export { mutationOptions } from './mutationOptions'
|
|
46
47
|
export { useMutation } from './useMutation'
|
|
47
48
|
export { useInfiniteQuery } from './useInfiniteQuery'
|
|
48
49
|
export { useIsRestoring, IsRestoringProvider } from './isRestoring'
|
|
50
|
+
export { usePrefetchQuery } from './usePrefetchQuery'
|
|
51
|
+
export { usePrefetchInfiniteQuery } from './usePrefetchInfiniteQuery'
|