Skip to content

Commit 98805c8

Browse files
manudelidanielpza
andcommitted
feat(query-core, react-query): backport some v5 apis to v4
Co-authored-by: Daniel Perez <[email protected]>
1 parent 4e6432a commit 98805c8

15 files changed

+2571
-135
lines changed

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v16.19.0
1+
22.12.0

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"eslint-plugin-import": "^2.27.5",
7676
"eslint-plugin-react": "^7.32.2",
7777
"eslint-plugin-react-hooks": "^4.6.0",
78+
"expect-type": "^1.2.1",
7879
"git-log-parser": "^1.2.0",
7980
"jest": "^27.5.1",
8081
"jsonfile": "^6.1.0",

packages/query-core/src/mutationObserver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,16 @@ export class MutationObserver<
141141
? this.currentMutation.state
142142
: getDefaultState<TData, TError, TVariables, TContext>()
143143

144+
const isLoading = state.status === 'loading'
144145
const result: MutationObserverBaseResult<
145146
TData,
146147
TError,
147148
TVariables,
148149
TContext
149150
> = {
150151
...state,
151-
isLoading: state.status === 'loading',
152+
isLoading,
153+
isPending: isLoading,
152154
isSuccess: state.status === 'success',
153155
isError: state.status === 'error',
154156
isIdle: state.status === 'idle',

packages/query-core/src/tests/mutations.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ describe('mutations', () => {
7676
isError: false,
7777
isIdle: true,
7878
isLoading: false,
79+
isPending: false,
7980
isPaused: false,
8081
isSuccess: false,
8182
mutate: expect.any(Function),
@@ -103,6 +104,7 @@ describe('mutations', () => {
103104
isError: false,
104105
isIdle: false,
105106
isLoading: true,
107+
isPending: true,
106108
isPaused: false,
107109
isSuccess: false,
108110
mutate: expect.any(Function),
@@ -122,6 +124,7 @@ describe('mutations', () => {
122124
isError: false,
123125
isIdle: false,
124126
isLoading: true,
127+
isPending: true,
125128
isPaused: false,
126129
isSuccess: false,
127130
mutate: expect.any(Function),
@@ -141,6 +144,7 @@ describe('mutations', () => {
141144
isError: false,
142145
isIdle: false,
143146
isLoading: false,
147+
isPending: false,
144148
isPaused: false,
145149
isSuccess: true,
146150
mutate: expect.any(Function),
@@ -181,6 +185,7 @@ describe('mutations', () => {
181185
isError: false,
182186
isIdle: false,
183187
isLoading: true,
188+
isPending: true,
184189
isPaused: false,
185190
isSuccess: false,
186191
mutate: expect.any(Function),
@@ -200,6 +205,7 @@ describe('mutations', () => {
200205
isError: false,
201206
isIdle: false,
202207
isLoading: true,
208+
isPending: true,
203209
isPaused: false,
204210
isSuccess: false,
205211
mutate: expect.any(Function),
@@ -219,6 +225,7 @@ describe('mutations', () => {
219225
isError: false,
220226
isIdle: false,
221227
isLoading: true,
228+
isPending: true,
222229
isPaused: false,
223230
isSuccess: false,
224231
mutate: expect.any(Function),
@@ -238,6 +245,7 @@ describe('mutations', () => {
238245
isError: true,
239246
isIdle: false,
240247
isLoading: false,
248+
isPending: false,
241249
isPaused: false,
242250
isSuccess: false,
243251
mutate: expect.any(Function),

packages/query-core/src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import type { QueryCache } from './queryCache'
88
import type { MutationCache } from './mutationCache'
99
import type { Logger } from './logger'
1010

11+
export type OmitKeyof<
12+
TObject,
13+
TKey extends TStrictly extends 'safely'
14+
?
15+
| keyof TObject
16+
| (string & Record<never, never>)
17+
| (number & Record<never, never>)
18+
| (symbol & Record<never, never>)
19+
: keyof TObject,
20+
TStrictly extends 'strictly' | 'safely' = 'strictly',
21+
> = Omit<TObject, TKey>
22+
1123
export type QueryKey = readonly unknown[]
1224

1325
export type QueryFunction<
@@ -646,6 +658,7 @@ export interface MutationObserverBaseResult<
646658
isError: boolean
647659
isIdle: boolean
648660
isLoading: boolean
661+
isPending: boolean
649662
isSuccess: boolean
650663
mutate: MutateFunction<TData, TError, TVariables, TContext>
651664
reset: () => void
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expectTypeOf } from 'expect-type'
2+
import {
3+
type UseQueryResult,
4+
useQueries,
5+
useQuery,
6+
useQueryClient,
7+
} from '@tanstack/react-query'
8+
import { queryOptions } from '..'
9+
import { useSuspenseQueries, useSuspenseQuery } from '..'
10+
import { type UseSuspenseQueryResult } from '../useSuspenseQuery'
11+
import { doNotExecute } from './utils'
12+
13+
const queryKey = ['key'] as const
14+
15+
const query = {
16+
options1: () =>
17+
queryOptions({
18+
queryKey: [...queryKey, 1] as const,
19+
queryFn: () => Promise.resolve({ field: 'success' }),
20+
}),
21+
options2: () =>
22+
queryOptions({
23+
queryKey: [...queryKey, 2] as const,
24+
queryFn: () => Promise.resolve({ field: 'success' }),
25+
}),
26+
}
27+
28+
describe('queryOptions', () => {
29+
it('should be used with useQuery', () => {
30+
doNotExecute(() => {
31+
const keyFn1Query = useQuery(query.options1())
32+
expectTypeOf(keyFn1Query).toEqualTypeOf<
33+
UseQueryResult<{ field: string }>
34+
>()
35+
expectTypeOf(keyFn1Query.data).toEqualTypeOf<
36+
{ field: string } | undefined
37+
>()
38+
const keyFn1Query_Select = useQuery({
39+
...query.options1(),
40+
select: (data) => data.field,
41+
})
42+
expectTypeOf(keyFn1Query_Select).toEqualTypeOf<UseQueryResult<string>>()
43+
expectTypeOf(keyFn1Query_Select.data).toEqualTypeOf<string | undefined>()
44+
})
45+
})
46+
it('should be used with useSuspenseQuery', () => {
47+
doNotExecute(() => {
48+
const keyFn1SuspenseQuery = useSuspenseQuery(query.options1())
49+
expectTypeOf(keyFn1SuspenseQuery).toEqualTypeOf<
50+
UseSuspenseQueryResult<{ field: string }>
51+
>()
52+
expectTypeOf(keyFn1SuspenseQuery.data).toEqualTypeOf<{ field: string }>()
53+
const keyFn1SuspenseQuery_Select = useSuspenseQuery({
54+
...query.options1(),
55+
select: (data) => data.field,
56+
})
57+
expectTypeOf(keyFn1SuspenseQuery_Select).toEqualTypeOf<
58+
UseSuspenseQueryResult<string>
59+
>()
60+
expectTypeOf(keyFn1SuspenseQuery_Select.data).toEqualTypeOf<string>()
61+
})
62+
})
63+
it('should be used with useQueries', () => {
64+
doNotExecute(() => {
65+
const [query1, query2, query3] = useQueries({
66+
queries: [
67+
query.options1(),
68+
{ ...query.options2() },
69+
queryOptions({
70+
queryKey: [...queryKey, 4] as const,
71+
queryFn: () => Promise.resolve({ field: 'success' }),
72+
select: (data) => {
73+
expectTypeOf(data).toEqualTypeOf<{ field: string }>()
74+
return data.field
75+
},
76+
}),
77+
],
78+
})
79+
expectTypeOf(query1).toEqualTypeOf<UseQueryResult<{ field: string }>>()
80+
expectTypeOf(query1.data).toEqualTypeOf<{ field: string } | undefined>()
81+
expectTypeOf(query2).toEqualTypeOf<UseQueryResult<{ field: string }>>()
82+
expectTypeOf(query2.data).toEqualTypeOf<{ field: string } | undefined>()
83+
expectTypeOf(query3).toEqualTypeOf<UseQueryResult<string>>()
84+
expectTypeOf(query3.data).toEqualTypeOf<string | undefined>()
85+
})
86+
})
87+
it('should be used with useSuspenseQueries', () => {
88+
doNotExecute(() => {
89+
const [query1, query2, query3] = useSuspenseQueries({
90+
queries: [
91+
query.options1(),
92+
{ ...query.options2() },
93+
queryOptions({
94+
queryKey: [...queryKey, 4] as const,
95+
queryFn: () => Promise.resolve({ field: 'success' }),
96+
select: (data) => {
97+
expectTypeOf(data).toEqualTypeOf<{ field: string }>()
98+
return data.field
99+
},
100+
}),
101+
],
102+
})
103+
104+
expectTypeOf(query1).toEqualTypeOf<
105+
UseSuspenseQueryResult<{ field: string }>
106+
>()
107+
expectTypeOf(query1.data).toEqualTypeOf<{ field: string }>()
108+
expectTypeOf(query2).toEqualTypeOf<
109+
UseSuspenseQueryResult<{ field: string }>
110+
>()
111+
expectTypeOf(query2.data).toEqualTypeOf<{ field: string }>()
112+
expectTypeOf(query3).toEqualTypeOf<UseSuspenseQueryResult<string>>()
113+
expectTypeOf(query3.data).toEqualTypeOf<string>()
114+
})
115+
})
116+
it('should be used with useQueryClient', () => {
117+
doNotExecute(async () => {
118+
const queryClient = useQueryClient()
119+
120+
queryClient.invalidateQueries(query.options1())
121+
queryClient.resetQueries(query.options1())
122+
queryClient.removeQueries(query.options1())
123+
queryClient.cancelQueries(query.options1())
124+
queryClient.prefetchQuery(query.options1())
125+
queryClient.refetchQueries(query.options1())
126+
127+
const query1 = await queryClient.fetchQuery(query.options1())
128+
expectTypeOf(query1).toEqualTypeOf<{ field: string }>()
129+
})
130+
})
131+
})

packages/react-query/src/__tests__/useQuery.types.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useQuery } from '../useQuery'
2+
import { doNotExecute } from './utils'
23

34
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
45
T,
@@ -8,8 +9,6 @@ export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
89

910
export type Expect<T extends true> = T
1011

11-
const doNotExecute = (_func: () => void) => true
12-
1312
describe('initialData', () => {
1413
describe('Config object overload', () => {
1514
it('TData should always be defined when initialData is provided as an object', () => {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { expectTypeOf } from 'expect-type'
2+
import { queryOptions } from '..'
3+
import { useSuspenseQueries } from '..'
4+
import { doNotExecute } from './utils'
5+
import type { UseSuspenseQueryResult } from '../useSuspenseQuery'
6+
7+
export const queryKey = ['key'] as const
8+
const sleep = (ms: number) =>
9+
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), ms))
10+
export const queryFn = () => sleep(10).then(() => ({ text: 'response' }))
11+
export const select = (data: Awaited<ReturnType<typeof queryFn>>) => data.text
12+
13+
describe('useSuspenseQueries', () => {
14+
it('type check', () => {
15+
doNotExecute(() => {
16+
useSuspenseQueries({
17+
queries: [
18+
{
19+
queryKey: [...queryKey, 1] as const,
20+
queryFn,
21+
// @ts-expect-error no suspense
22+
suspense: false,
23+
},
24+
] as const,
25+
})
26+
useSuspenseQueries({
27+
queries: [
28+
{
29+
queryKey: [...queryKey, 2] as const,
30+
queryFn,
31+
select,
32+
// @ts-expect-error no suspense
33+
suspense: true,
34+
},
35+
] as const,
36+
})
37+
useSuspenseQueries({
38+
queries: [
39+
{
40+
queryKey: [...queryKey, 3] as const,
41+
queryFn,
42+
// @ts-expect-error no enabled
43+
enabled: true,
44+
},
45+
] as const,
46+
})
47+
useSuspenseQueries({
48+
queries: [
49+
{
50+
queryKey: [...queryKey, 4] as const,
51+
queryFn,
52+
// @ts-expect-error no enabled
53+
enabled: true,
54+
select,
55+
},
56+
] as const,
57+
})
58+
useSuspenseQueries({
59+
queries: [
60+
{
61+
queryKey: [...queryKey, 4] as const,
62+
queryFn,
63+
// @ts-expect-error no networkMode
64+
networkMode: 'always',
65+
select,
66+
},
67+
] as const,
68+
})
69+
useSuspenseQueries({
70+
queries: [
71+
queryOptions({
72+
queryKey: [...queryKey, 4] as const,
73+
queryFn: () => Promise.resolve({ field: 'success' }),
74+
select: (data) => data.field,
75+
}),
76+
] as const,
77+
})
78+
// @ts-expect-error if no items
79+
useSuspenseQueries({})
80+
// @ts-expect-error if no items
81+
useSuspenseQueries()
82+
83+
const [query, selectedQuery, selectedQueryByQueryOptions] =
84+
useSuspenseQueries({
85+
queries: [
86+
{ queryKey: [...queryKey, 5] as const, queryFn },
87+
{ queryKey: [...queryKey, 6] as const, queryFn, select },
88+
queryOptions({
89+
queryKey: [...queryKey, 4] as const,
90+
queryFn: () => Promise.resolve({ field: 'success' }),
91+
select: (data) => data.field,
92+
}),
93+
] as const,
94+
})
95+
96+
expectTypeOf(query).toEqualTypeOf<
97+
UseSuspenseQueryResult<{ text: string }>
98+
>()
99+
expectTypeOf(query.status).toEqualTypeOf<`success`>()
100+
expectTypeOf(query.data).toEqualTypeOf<{ text: string }>()
101+
102+
expectTypeOf(selectedQuery).toEqualTypeOf<
103+
UseSuspenseQueryResult<string>
104+
>()
105+
expectTypeOf(selectedQuery.status).toEqualTypeOf<`success`>()
106+
expectTypeOf(selectedQuery.data).toEqualTypeOf<string>()
107+
108+
expectTypeOf(selectedQueryByQueryOptions).toEqualTypeOf<
109+
UseSuspenseQueryResult<string>
110+
>()
111+
expectTypeOf(
112+
selectedQueryByQueryOptions.status,
113+
).toEqualTypeOf<`success`>()
114+
expectTypeOf(selectedQueryByQueryOptions.data).toEqualTypeOf<string>()
115+
})
116+
})
117+
})

0 commit comments

Comments
 (0)