@pixotope/query 0.6.0 → 0.7.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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-test.log +9 -7
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs +61 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -77
- package/dist/index.d.ts +35 -77
- package/dist/index.js +61 -66
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/factories.test.ts +65 -0
- package/src/factories.ts +48 -31
- package/src/proxyBinders.ts +3 -1
- package/src/useMutation.test.ts +106 -0
- package/src/useMutation.ts +87 -116
- package/src/useQuery.test.ts +77 -139
- package/src/useQuery.ts +5 -8
- package/src/mock/useQuery.ts +0 -48
package/src/useMutation.ts
CHANGED
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
|
|
3
|
-
export type MutationFunction<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export type MutationFunction<
|
|
4
|
+
TData = unknown,
|
|
5
|
+
TParams = unknown,
|
|
6
|
+
TMetadata = unknown,
|
|
7
|
+
> = (params: TParams, meta?: TMetadata) => Promise<TData>;
|
|
7
8
|
|
|
8
9
|
export interface MutationOptions<
|
|
9
10
|
TData = unknown,
|
|
10
11
|
TError = unknown,
|
|
11
12
|
TParams = void,
|
|
13
|
+
TMetadata = unknown,
|
|
12
14
|
> {
|
|
13
|
-
mutationFn: MutationFunction<TData, TParams>;
|
|
15
|
+
mutationFn: MutationFunction<TData, TParams, TMetadata>;
|
|
14
16
|
params?: TParams;
|
|
15
|
-
onSuccess?: (
|
|
17
|
+
onSuccess?: (
|
|
18
|
+
data: TData,
|
|
19
|
+
variables: TParams,
|
|
20
|
+
meta?: TMetadata
|
|
21
|
+
) => Promise<unknown> | unknown;
|
|
16
22
|
onError?: (
|
|
17
23
|
error: TMutationError<TError>,
|
|
18
|
-
variables: TParams
|
|
24
|
+
variables: TParams,
|
|
25
|
+
meta?: TMetadata
|
|
19
26
|
) => Promise<unknown> | unknown;
|
|
20
27
|
onSettled?: (
|
|
21
28
|
data: TData | undefined,
|
|
22
29
|
error: TMutationError<TError> | null,
|
|
23
|
-
variables: TParams
|
|
30
|
+
variables: TParams,
|
|
31
|
+
meta?: TMetadata
|
|
24
32
|
) => Promise<unknown> | unknown;
|
|
25
33
|
compareOrThrow?: (result: TData | TError) => boolean;
|
|
26
34
|
timeout?: number;
|
|
27
|
-
prepareParams?: (
|
|
35
|
+
prepareParams?: (
|
|
36
|
+
variables: TParams,
|
|
37
|
+
defaults?: TParams,
|
|
38
|
+
meta?: TMetadata
|
|
39
|
+
) => TParams;
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
type TMutationError<TError> = TError | Error | null;
|
|
@@ -33,13 +45,19 @@ export interface MutateOptions<
|
|
|
33
45
|
TData = unknown,
|
|
34
46
|
TError = unknown,
|
|
35
47
|
TParams = void,
|
|
48
|
+
TMetadata = unknown,
|
|
36
49
|
> {
|
|
37
|
-
onSuccess?: (data: TData, variables: TParams) => void;
|
|
38
|
-
onError?: (
|
|
50
|
+
onSuccess?: (data: TData, variables: TParams, meta?: TMetadata) => void;
|
|
51
|
+
onError?: (
|
|
52
|
+
error: TMutationError<TError>,
|
|
53
|
+
variables: TParams,
|
|
54
|
+
meta?: TMetadata
|
|
55
|
+
) => void;
|
|
39
56
|
onSettled?: (
|
|
40
57
|
data: TData | undefined,
|
|
41
58
|
error: TMutationError<TError> | null,
|
|
42
|
-
variables: TParams
|
|
59
|
+
variables: TParams,
|
|
60
|
+
meta?: TMetadata
|
|
43
61
|
) => void;
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -61,58 +79,13 @@ export class ComparisonFailError<TPayload = unknown> extends Error {
|
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
|
|
64
|
-
/**
|
|
65
|
-
* `useMutation` is a hook that allows you to perform a mutation/push updates to the services while
|
|
66
|
-
* providing extensive API to tap into the status and results of the mutation.
|
|
67
|
-
* @param mutationFn The mutation function to be called when the `mutate` function is called.
|
|
68
|
-
* `useMutation` does not care about what the function does, all it needs it a function that returns
|
|
69
|
-
* a promise.
|
|
70
|
-
* @param options Options to configure the mutation.
|
|
71
|
-
* @returns An object with the following properties:
|
|
72
|
-
* - `mutateAsync`: A function that takes in the variables and returns a promise that resolves to the
|
|
73
|
-
* result of the mutation. This function can be awaited to get the result of the mutation. Callbacks can
|
|
74
|
-
* be passed to this function to be called when the mutation is successful or when it fails. It's good idea
|
|
75
|
-
* to wrap this function in a try/catch block.
|
|
76
|
-
* - `mutate`: A function that takes in the variables and returns nothing. This function does not
|
|
77
|
-
* return a promise. Callbacks can be passed to this function to be called when the mutation is
|
|
78
|
-
* successful or when it fails.
|
|
79
|
-
* - `status`: The status of the mutation. Can be one of `idle`, `loading`, `success` or `error`.
|
|
80
|
-
* - `result`: The result of the mutation. This is undefined until the mutation is successful.
|
|
81
|
-
* - `reset`: A function that resets the mutation to the initial state.
|
|
82
|
-
* - `error`: The error object if the mutation has failed.
|
|
83
|
-
* @example
|
|
84
|
-
* ```typescript
|
|
85
|
-
* const mutationRes = useMutation({
|
|
86
|
-
* mutationFn: (variables) => {
|
|
87
|
-
* return ZMQHelper.callSomethingAsync(variables)
|
|
88
|
-
* },
|
|
89
|
-
* onSuccess: (data) => {
|
|
90
|
-
* // do something with the result
|
|
91
|
-
* },
|
|
92
|
-
* onError: (error) => {
|
|
93
|
-
* // do something with the error
|
|
94
|
-
* },
|
|
95
|
-
* onSettled: (data, error) => {
|
|
96
|
-
* // do something when the mutation is done
|
|
97
|
-
* }});
|
|
98
|
-
*
|
|
99
|
-
* // call the mutation
|
|
100
|
-
* const res = await mutationRes.mutateAsync({ foo: "bar" });
|
|
101
|
-
* // OR
|
|
102
|
-
* mutationRes.mutate({ foo: "bar" }, {
|
|
103
|
-
* onSuccess: (data) => {
|
|
104
|
-
* // do something with the result
|
|
105
|
-
* // for example show a toast message
|
|
106
|
-
* // stop local loading state
|
|
107
|
-
* });
|
|
108
|
-
* ```
|
|
109
|
-
*/
|
|
110
82
|
export const useMutation = <
|
|
111
83
|
TData = unknown,
|
|
112
84
|
TError = TMutationError<unknown>,
|
|
113
85
|
TParams = void,
|
|
86
|
+
TMetadata = unknown,
|
|
114
87
|
>(
|
|
115
|
-
options: MutationOptions<TData, TError, TParams>
|
|
88
|
+
options: MutationOptions<TData, TError, TParams, TMetadata>
|
|
116
89
|
) => {
|
|
117
90
|
const {
|
|
118
91
|
mutationFn,
|
|
@@ -142,79 +115,77 @@ export const useMutation = <
|
|
|
142
115
|
const internalCallbackRef = useRef(internalCallbackRefistry);
|
|
143
116
|
internalCallbackRef.current = internalCallbackRefistry;
|
|
144
117
|
|
|
145
|
-
const mutateAsync = useCallback(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
internalCallbackRef.current.prepareParams
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
118
|
+
const mutateAsync = useCallback(async (params: TParams, meta?: TMetadata) => {
|
|
119
|
+
setStatus("loading");
|
|
120
|
+
setError(null);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const promises: RacePromises<TData>[] = [
|
|
124
|
+
mutationFnRef.current(
|
|
125
|
+
internalCallbackRef.current.prepareParams
|
|
126
|
+
? internalCallbackRef.current.prepareParams(
|
|
127
|
+
params,
|
|
128
|
+
internalOptionsRef.current.params,
|
|
129
|
+
meta
|
|
130
|
+
)
|
|
131
|
+
: params,
|
|
132
|
+
meta
|
|
133
|
+
),
|
|
134
|
+
...(internalOptionsRef.current.timeout
|
|
135
|
+
? [
|
|
136
|
+
new Promise<RaceErrorReason>((resolve) =>
|
|
137
|
+
setTimeout(
|
|
138
|
+
() => resolve("timeout"),
|
|
139
|
+
internalOptionsRef.current.timeout
|
|
157
140
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
),
|
|
161
|
-
|
|
162
|
-
? [
|
|
163
|
-
new Promise<RaceErrorReason>((resolve) =>
|
|
164
|
-
setTimeout(
|
|
165
|
-
() => resolve("timeout"),
|
|
166
|
-
internalOptionsRef.current.timeout
|
|
167
|
-
)
|
|
168
|
-
),
|
|
169
|
-
]
|
|
170
|
-
: []),
|
|
171
|
-
];
|
|
172
|
-
|
|
173
|
-
const res = await Promise.race(promises);
|
|
174
|
-
|
|
175
|
-
if (res === "timeout") {
|
|
176
|
-
throw new Error("timeout");
|
|
177
|
-
}
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
: []),
|
|
144
|
+
];
|
|
178
145
|
|
|
179
|
-
|
|
180
|
-
throw new ComparisonFailError(JSON.stringify(res), res);
|
|
181
|
-
}
|
|
146
|
+
const res = await Promise.race(promises);
|
|
182
147
|
|
|
183
|
-
|
|
184
|
-
|
|
148
|
+
if (res === "timeout") {
|
|
149
|
+
throw new Error("timeout");
|
|
150
|
+
}
|
|
185
151
|
|
|
186
|
-
|
|
187
|
-
|
|
152
|
+
if (internalCallbackRef.current.compareOrThrow?.(res)) {
|
|
153
|
+
throw new ComparisonFailError(JSON.stringify(res), res);
|
|
154
|
+
}
|
|
188
155
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
setError(error);
|
|
192
|
-
setStatus("error");
|
|
156
|
+
setResult(res);
|
|
157
|
+
setStatus("success");
|
|
193
158
|
|
|
194
|
-
|
|
195
|
-
|
|
159
|
+
internalCallbackRef.current.onSuccess?.(res, params, meta);
|
|
160
|
+
internalCallbackRef.current.onSettled?.(res, null, params, meta);
|
|
196
161
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
162
|
+
return res;
|
|
163
|
+
} catch (error: any) {
|
|
164
|
+
setError(error);
|
|
165
|
+
setStatus("error");
|
|
166
|
+
|
|
167
|
+
internalCallbackRef.current.onError?.(error, params, meta);
|
|
168
|
+
internalCallbackRef.current.onSettled?.(undefined, error, params, meta);
|
|
169
|
+
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}, []);
|
|
202
173
|
|
|
203
174
|
const mutate = useCallback(
|
|
204
175
|
(
|
|
205
176
|
variables: TParams,
|
|
206
|
-
options?: MutateOptions<TData, TError, TParams> & {
|
|
207
|
-
|
|
177
|
+
options?: MutateOptions<TData, TError, TParams, TMetadata> & {
|
|
178
|
+
meta?: TMetadata;
|
|
208
179
|
}
|
|
209
180
|
) => {
|
|
210
|
-
mutateAsync(variables, options?.
|
|
181
|
+
mutateAsync(variables, options?.meta).then(
|
|
211
182
|
(data) => {
|
|
212
|
-
options?.onSuccess?.(data, variables);
|
|
213
|
-
options?.onSettled?.(data, null, variables);
|
|
183
|
+
options?.onSuccess?.(data, variables, options?.meta);
|
|
184
|
+
options?.onSettled?.(data, null, variables, options?.meta);
|
|
214
185
|
},
|
|
215
186
|
(error) => {
|
|
216
|
-
options?.onError?.(error, variables);
|
|
217
|
-
options?.onSettled?.(undefined, error, variables);
|
|
187
|
+
options?.onError?.(error, variables, options?.meta);
|
|
188
|
+
options?.onSettled?.(undefined, error, variables, options?.meta);
|
|
218
189
|
}
|
|
219
190
|
);
|
|
220
191
|
},
|
package/src/useQuery.test.ts
CHANGED
|
@@ -1,210 +1,162 @@
|
|
|
1
1
|
import { fireEvent } from "@testing-library/dom";
|
|
2
2
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
3
|
-
import { vi, describe,
|
|
3
|
+
import { vi, describe, it, expect, test } from "vitest";
|
|
4
4
|
|
|
5
5
|
import { useQuery } from "./useQuery";
|
|
6
|
-
import { MockUseQueryUtils } from "./mock/useQuery";
|
|
7
6
|
const mockQueryKey = ["dummyQK"];
|
|
8
7
|
|
|
9
|
-
function until(condition: boolean) {
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
if (condition) {
|
|
12
|
-
resolve(null);
|
|
13
|
-
} else {
|
|
14
|
-
reject();
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
8
|
describe("useQuery", () => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
useQuery({
|
|
30
|
-
queryFn: mockFunction,
|
|
31
|
-
key: mockQueryKey,
|
|
32
|
-
})
|
|
9
|
+
it("should call queryFn by on mount by default", async () => {
|
|
10
|
+
const mockFunction = vi.fn();
|
|
11
|
+
await act(() =>
|
|
12
|
+
renderHook(() =>
|
|
13
|
+
useQuery({
|
|
14
|
+
queryFn: mockFunction,
|
|
15
|
+
key: mockQueryKey,
|
|
16
|
+
})
|
|
17
|
+
)
|
|
33
18
|
);
|
|
34
|
-
|
|
35
19
|
expect(mockFunction).toBeCalledTimes(1);
|
|
36
20
|
});
|
|
37
|
-
it("should be in loading state on mount", () => {
|
|
21
|
+
it("should be in loading state on mount", async () => {
|
|
38
22
|
const { result } = renderHook(() =>
|
|
39
23
|
useQuery({
|
|
40
|
-
queryFn:
|
|
24
|
+
queryFn: vi.fn(),
|
|
41
25
|
key: mockQueryKey,
|
|
42
26
|
})
|
|
43
27
|
);
|
|
44
28
|
|
|
45
|
-
expect(result.current.status).toBe("loading");
|
|
29
|
+
await waitFor(() => expect(result.current.status).toBe("loading"));
|
|
46
30
|
});
|
|
47
31
|
it("should be in success state after queryFn resolves", async () => {
|
|
32
|
+
const mockFunction = vi.fn();
|
|
48
33
|
const { result } = renderHook(() =>
|
|
49
34
|
useQuery({
|
|
50
|
-
queryFn:
|
|
35
|
+
queryFn: mockFunction,
|
|
51
36
|
key: mockQueryKey,
|
|
52
37
|
})
|
|
53
38
|
);
|
|
54
39
|
act(() => {
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
await waitFor(() => until(result.current.status === "success"), {
|
|
58
|
-
timeout: 1000,
|
|
40
|
+
mockFunction.mockResolvedValue("test");
|
|
59
41
|
});
|
|
42
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
60
43
|
});
|
|
61
44
|
it("should be in error state after queryFn rejects", async () => {
|
|
45
|
+
const mockFunction = vi.fn().mockRejectedValue("test");
|
|
62
46
|
const { result } = renderHook(() =>
|
|
63
47
|
useQuery({
|
|
64
|
-
queryFn:
|
|
48
|
+
queryFn: mockFunction,
|
|
65
49
|
key: mockQueryKey,
|
|
66
50
|
})
|
|
67
51
|
);
|
|
68
|
-
|
|
69
|
-
await waitFor(() => until(result.current.status === "error"), {
|
|
70
|
-
timeout: 1000,
|
|
71
|
-
});
|
|
72
|
-
expect(result.current.status).toBe("error");
|
|
52
|
+
await waitFor(() => expect(result.current.status).toBe("error"));
|
|
73
53
|
});
|
|
74
54
|
it("should be in timeout state after queryFn rejects with timeout", async () => {
|
|
55
|
+
const mockFunction = vi.fn(
|
|
56
|
+
() => new Promise((_, reject) => setTimeout(() => reject("test"), 10))
|
|
57
|
+
);
|
|
75
58
|
const { result } = renderHook(() =>
|
|
76
59
|
useQuery({
|
|
77
|
-
queryFn:
|
|
60
|
+
queryFn: mockFunction,
|
|
78
61
|
key: mockQueryKey,
|
|
79
62
|
timeout: 0.01,
|
|
80
63
|
})
|
|
81
64
|
);
|
|
82
|
-
await waitFor(() =>
|
|
83
|
-
timeout: 1000,
|
|
84
|
-
});
|
|
85
|
-
expect(result.current.status === "error");
|
|
65
|
+
await waitFor(() => expect(result.current.status).toBe("error"));
|
|
86
66
|
});
|
|
87
67
|
it("should be in success state after queryFn resolves with initialData", async () => {
|
|
88
68
|
const { result } = renderHook(() =>
|
|
89
69
|
useQuery({
|
|
90
|
-
queryFn: ()
|
|
70
|
+
queryFn: vi.fn().mockResolvedValue("test-test-2"),
|
|
91
71
|
key: mockQueryKey,
|
|
92
72
|
initialData: "test-test",
|
|
93
73
|
})
|
|
94
74
|
);
|
|
95
|
-
|
|
96
|
-
await waitFor(() => until(result.current.status === "success"), {
|
|
97
|
-
timeout: 1000,
|
|
98
|
-
});
|
|
75
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
99
76
|
});
|
|
100
77
|
it("should be in idle state if enabled is false", async () => {
|
|
101
78
|
const { result } = renderHook(() =>
|
|
102
79
|
useQuery({
|
|
103
|
-
queryFn:
|
|
80
|
+
queryFn: vi.fn(),
|
|
104
81
|
key: mockQueryKey,
|
|
105
82
|
enabled: false,
|
|
106
83
|
})
|
|
107
84
|
);
|
|
108
|
-
expect(result.current.status).toBe("idle");
|
|
85
|
+
await waitFor(() => expect(result.current.status).toBe("idle"));
|
|
109
86
|
});
|
|
110
87
|
test("refetch should trigger query function", async () => {
|
|
111
|
-
const resolver = vi.fn(
|
|
112
|
-
const { result } =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
118
|
-
)
|
|
88
|
+
const resolver = vi.fn().mockResolvedValue("test");
|
|
89
|
+
const { result } = renderHook(() =>
|
|
90
|
+
useQuery({
|
|
91
|
+
queryFn: resolver,
|
|
92
|
+
key: mockQueryKey,
|
|
93
|
+
})
|
|
119
94
|
);
|
|
120
|
-
await
|
|
121
|
-
await
|
|
122
|
-
timeout: 1000,
|
|
123
|
-
});
|
|
124
|
-
await act(() => result.current.refetch());
|
|
95
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
96
|
+
await act(() => result.current.refetch({}));
|
|
125
97
|
expect(resolver).toBeCalledTimes(2);
|
|
126
98
|
});
|
|
127
|
-
test("select should return selected
|
|
128
|
-
const
|
|
129
|
-
foo: "bar",
|
|
130
|
-
baz: "qux",
|
|
131
|
-
});
|
|
99
|
+
test("select should return correctly selected data", async () => {
|
|
100
|
+
const resolver = vi.fn().mockResolvedValue({ foo: "bar" });
|
|
132
101
|
const { result } = renderHook(() =>
|
|
133
102
|
useQuery({
|
|
134
|
-
queryFn:
|
|
103
|
+
queryFn: resolver,
|
|
135
104
|
key: mockQueryKey,
|
|
136
|
-
select: (data) => data?.foo,
|
|
105
|
+
select: (data: any) => data?.foo,
|
|
137
106
|
})
|
|
138
107
|
);
|
|
139
|
-
expect(result.current.data).
|
|
140
|
-
queryMock.resolve();
|
|
141
|
-
await waitFor(() => until(result.current.data !== undefined), {
|
|
142
|
-
timeout: 1000,
|
|
143
|
-
});
|
|
144
|
-
expect(result.current.data).toBe("bar");
|
|
108
|
+
await waitFor(() => expect(result.current.data).toBe("bar"));
|
|
145
109
|
});
|
|
146
110
|
test("onSuccess should be called after queryFn resolves", async () => {
|
|
147
111
|
const onSuccess = vi.fn();
|
|
148
112
|
renderHook(() =>
|
|
149
113
|
useQuery({
|
|
150
|
-
queryFn: ()
|
|
114
|
+
queryFn: vi.fn().mockResolvedValue("test"),
|
|
151
115
|
key: mockQueryKey,
|
|
152
116
|
onSuccess,
|
|
153
117
|
})
|
|
154
118
|
);
|
|
155
|
-
await
|
|
156
|
-
|
|
157
|
-
timeout: 1000,
|
|
158
|
-
});
|
|
159
|
-
expect(onSuccess).toBeCalledTimes(1);
|
|
119
|
+
await waitFor(() => expect(onSuccess).toBeCalledTimes(1));
|
|
120
|
+
expect(onSuccess).toBeCalledWith("test");
|
|
160
121
|
});
|
|
161
122
|
test("onError should be called after queryFn rejects", async () => {
|
|
162
123
|
const onError = vi.fn();
|
|
163
124
|
renderHook(() =>
|
|
164
125
|
useQuery({
|
|
165
|
-
queryFn: ()
|
|
126
|
+
queryFn: vi.fn().mockRejectedValue("test"),
|
|
166
127
|
key: mockQueryKey,
|
|
167
128
|
onError,
|
|
168
129
|
})
|
|
169
130
|
);
|
|
170
|
-
await
|
|
171
|
-
|
|
172
|
-
timeout: 1000,
|
|
173
|
-
});
|
|
174
|
-
expect(onError).toBeCalledTimes(1);
|
|
131
|
+
await waitFor(() => expect(onError).toBeCalledTimes(1));
|
|
132
|
+
expect(onError).toBeCalledWith("test");
|
|
175
133
|
});
|
|
176
134
|
test("onSettled should be called after queryFn resolves", async () => {
|
|
177
135
|
const onSettled = vi.fn();
|
|
178
136
|
renderHook(() =>
|
|
179
137
|
useQuery({
|
|
180
|
-
queryFn: ()
|
|
138
|
+
queryFn: vi.fn().mockResolvedValue("test"),
|
|
181
139
|
key: mockQueryKey,
|
|
182
140
|
onSettled,
|
|
183
141
|
})
|
|
184
142
|
);
|
|
185
|
-
await
|
|
186
|
-
|
|
187
|
-
timeout: 1000,
|
|
188
|
-
});
|
|
189
|
-
expect(onSettled).toBeCalledTimes(1);
|
|
143
|
+
await waitFor(() => expect(onSettled).toBeCalledTimes(1));
|
|
144
|
+
expect(onSettled).toBeCalledWith("test", undefined);
|
|
190
145
|
});
|
|
191
146
|
test("onSettled should be called after queryFn rejects", async () => {
|
|
192
147
|
const onSettled = vi.fn();
|
|
193
148
|
renderHook(() =>
|
|
194
149
|
useQuery({
|
|
195
|
-
queryFn: ()
|
|
150
|
+
queryFn: vi.fn().mockRejectedValue("test"),
|
|
196
151
|
key: mockQueryKey,
|
|
197
152
|
onSettled,
|
|
198
153
|
})
|
|
199
154
|
);
|
|
200
|
-
await
|
|
201
|
-
|
|
202
|
-
timeout: 1000,
|
|
203
|
-
});
|
|
204
|
-
expect(onSettled).toBeCalledTimes(1);
|
|
155
|
+
await waitFor(() => expect(onSettled).toBeCalledTimes(1));
|
|
156
|
+
expect(onSettled).toBeCalledWith(undefined, "test");
|
|
205
157
|
});
|
|
206
158
|
test("refetch is triggered on window focus", async () => {
|
|
207
|
-
const resolver = vi.fn(
|
|
159
|
+
const resolver = vi.fn().mockResolvedValue("test");
|
|
208
160
|
const { result } = await act(() =>
|
|
209
161
|
renderHook(() =>
|
|
210
162
|
useQuery({
|
|
@@ -214,15 +166,12 @@ describe("useQuery", () => {
|
|
|
214
166
|
})
|
|
215
167
|
)
|
|
216
168
|
);
|
|
217
|
-
await
|
|
218
|
-
await waitFor(() => until(result.current.status === "success"), {
|
|
219
|
-
timeout: 1000,
|
|
220
|
-
});
|
|
169
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
221
170
|
await act(() => fireEvent(window, new FocusEvent("focus")));
|
|
222
171
|
expect(resolver).toBeCalledTimes(2);
|
|
223
172
|
});
|
|
224
173
|
test("refetch interval should work", async () => {
|
|
225
|
-
const resolver = vi.fn(
|
|
174
|
+
const resolver = vi.fn().mockResolvedValue("test");
|
|
226
175
|
const { result } = renderHook(() =>
|
|
227
176
|
useQuery({
|
|
228
177
|
queryFn: resolver,
|
|
@@ -230,38 +179,31 @@ describe("useQuery", () => {
|
|
|
230
179
|
key: mockQueryKey,
|
|
231
180
|
})
|
|
232
181
|
);
|
|
233
|
-
await
|
|
234
|
-
await waitFor(() =>
|
|
235
|
-
timeout: 1000,
|
|
236
|
-
});
|
|
237
|
-
waitFor(() => until(resolver.call.length > 1), { timeout: 1000 });
|
|
182
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
183
|
+
await waitFor(() => expect(resolver.mock.calls.length).toBeGreaterThan(1));
|
|
238
184
|
});
|
|
239
185
|
test("refetch should automatically trigger if queryKey is changed", async () => {
|
|
240
|
-
const resolver = vi.fn(
|
|
241
|
-
const { result, rerender } =
|
|
242
|
-
|
|
243
|
-
(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
);
|
|
255
|
-
await act(() => defaultQueryMock.resolve());
|
|
256
|
-
await waitFor(() => until(result.current.status === "success"), {
|
|
257
|
-
timeout: 1000,
|
|
258
|
-
});
|
|
186
|
+
const resolver = vi.fn().mockResolvedValue("test");
|
|
187
|
+
const { result, rerender } = renderHook(
|
|
188
|
+
(props) =>
|
|
189
|
+
useQuery({
|
|
190
|
+
queryFn: resolver,
|
|
191
|
+
key: props.key,
|
|
192
|
+
}),
|
|
193
|
+
{
|
|
194
|
+
initialProps: {
|
|
195
|
+
key: ["dummyQK1"],
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
200
|
+
await waitFor(() => expect(resolver).toBeCalledTimes(1));
|
|
259
201
|
rerender({ key: ["dummyQK2"] });
|
|
260
202
|
rerender({ key: ["dummyQK3"] });
|
|
261
|
-
expect(resolver).toBeCalledTimes(3);
|
|
203
|
+
await waitFor(() => expect(resolver).toBeCalledTimes(3));
|
|
262
204
|
});
|
|
263
205
|
test("stateless mode", async () => {
|
|
264
|
-
const resolver = vi.fn(
|
|
206
|
+
const resolver = vi.fn().mockResolvedValue("test");
|
|
265
207
|
const { result } = renderHook(() =>
|
|
266
208
|
useQuery({
|
|
267
209
|
queryFn: resolver,
|
|
@@ -269,11 +211,7 @@ describe("useQuery", () => {
|
|
|
269
211
|
stateless: true,
|
|
270
212
|
})
|
|
271
213
|
);
|
|
272
|
-
await
|
|
273
|
-
await waitFor(() => until(result.current.status === "success"), {
|
|
274
|
-
timeout: 1000,
|
|
275
|
-
});
|
|
276
|
-
|
|
214
|
+
await waitFor(() => expect(result.current.status).toBe("success"));
|
|
277
215
|
// Here ts should kick in and complain about but for testing purposes, all good
|
|
278
216
|
// @ts-ignore
|
|
279
217
|
expect(result.current.data).toBeUndefined();
|
package/src/useQuery.ts
CHANGED
|
@@ -259,14 +259,11 @@ export const useQuery = <
|
|
|
259
259
|
|
|
260
260
|
// If the query should be refetched on an interval, add an interval
|
|
261
261
|
if (refetchInterval > 0) {
|
|
262
|
-
const interval = setInterval(
|
|
263
|
-
()
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
Math.max(refetchInterval, 1000)
|
|
269
|
-
);
|
|
262
|
+
const interval = setInterval(() => {
|
|
263
|
+
if (shouldRefetch()) {
|
|
264
|
+
refetch();
|
|
265
|
+
}
|
|
266
|
+
}, refetchInterval);
|
|
270
267
|
|
|
271
268
|
cleanups.push(() => {
|
|
272
269
|
clearInterval(interval);
|