@pixotope/query 0.4.0 → 0.6.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 +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-test.log +5 -5
- package/CHANGELOG.md +13 -0
- package/dist/index.cjs +53 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -18
- package/dist/index.d.ts +14 -18
- package/dist/index.js +53 -61
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/factories.ts +17 -26
- package/src/useMutation.ts +22 -14
- package/src/useQuery.test.ts +4 -5
- package/src/useQuery.ts +49 -44
package/src/useMutation.ts
CHANGED
|
@@ -13,10 +13,13 @@ export interface MutationOptions<
|
|
|
13
13
|
mutationFn: MutationFunction<TData, TParams>;
|
|
14
14
|
params?: TParams;
|
|
15
15
|
onSuccess?: (data: TData, variables: TParams) => Promise<unknown> | unknown;
|
|
16
|
-
onError?: (
|
|
16
|
+
onError?: (
|
|
17
|
+
error: TMutationError<TError>,
|
|
18
|
+
variables: TParams
|
|
19
|
+
) => Promise<unknown> | unknown;
|
|
17
20
|
onSettled?: (
|
|
18
21
|
data: TData | undefined,
|
|
19
|
-
error: TError | null,
|
|
22
|
+
error: TMutationError<TError> | null,
|
|
20
23
|
variables: TParams
|
|
21
24
|
) => Promise<unknown> | unknown;
|
|
22
25
|
compareOrThrow?: (result: TData | TError) => boolean;
|
|
@@ -24,16 +27,18 @@ export interface MutationOptions<
|
|
|
24
27
|
prepareParams?: (variables: TParams, defaults?: TParams) => TParams;
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
type TMutationError<TError> = TError | Error | null;
|
|
31
|
+
|
|
27
32
|
export interface MutateOptions<
|
|
28
33
|
TData = unknown,
|
|
29
34
|
TError = unknown,
|
|
30
35
|
TParams = void,
|
|
31
36
|
> {
|
|
32
37
|
onSuccess?: (data: TData, variables: TParams) => void;
|
|
33
|
-
onError?: (error: TError
|
|
38
|
+
onError?: (error: TMutationError<TError>, variables: TParams) => void;
|
|
34
39
|
onSettled?: (
|
|
35
40
|
data: TData | undefined,
|
|
36
|
-
error: TError | null,
|
|
41
|
+
error: TMutationError<TError> | null,
|
|
37
42
|
variables: TParams
|
|
38
43
|
) => void;
|
|
39
44
|
}
|
|
@@ -102,7 +107,11 @@ export class ComparisonFailError<TPayload = unknown> extends Error {
|
|
|
102
107
|
* });
|
|
103
108
|
* ```
|
|
104
109
|
*/
|
|
105
|
-
export const useMutation = <
|
|
110
|
+
export const useMutation = <
|
|
111
|
+
TData = unknown,
|
|
112
|
+
TError = TMutationError<unknown>,
|
|
113
|
+
TParams = void,
|
|
114
|
+
>(
|
|
106
115
|
options: MutationOptions<TData, TError, TParams>
|
|
107
116
|
) => {
|
|
108
117
|
const {
|
|
@@ -116,7 +125,7 @@ export const useMutation = <TData = unknown, TError = unknown, TParams = void>(
|
|
|
116
125
|
timeout,
|
|
117
126
|
} = options;
|
|
118
127
|
const [status, setStatus] = useState<MutationStatus>("idle");
|
|
119
|
-
const [error, setError] = useState<TError
|
|
128
|
+
const [error, setError] = useState<TMutationError<TError>>(null);
|
|
120
129
|
const [result, setResult] = useState<TData | undefined>(undefined);
|
|
121
130
|
const mutationFnRef = useRef(mutationFn);
|
|
122
131
|
const internalOptionsRef = useRef({
|
|
@@ -182,13 +191,8 @@ export const useMutation = <TData = unknown, TError = unknown, TParams = void>(
|
|
|
182
191
|
setError(error);
|
|
183
192
|
setStatus("error");
|
|
184
193
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (internalCallbackRef.current.onSettled) {
|
|
190
|
-
internalCallbackRef.current.onSettled(undefined, error, params);
|
|
191
|
-
}
|
|
194
|
+
internalCallbackRef.current.onError?.(error, params);
|
|
195
|
+
internalCallbackRef.current.onSettled?.(undefined, error, params);
|
|
192
196
|
|
|
193
197
|
throw error;
|
|
194
198
|
}
|
|
@@ -222,7 +226,11 @@ export const useMutation = <TData = unknown, TError = unknown, TParams = void>(
|
|
|
222
226
|
status,
|
|
223
227
|
result,
|
|
224
228
|
mutate,
|
|
225
|
-
reset: () =>
|
|
229
|
+
reset: () => {
|
|
230
|
+
setStatus("idle");
|
|
231
|
+
setError(null);
|
|
232
|
+
setResult(undefined);
|
|
233
|
+
},
|
|
226
234
|
error,
|
|
227
235
|
};
|
|
228
236
|
};
|
package/src/useQuery.test.ts
CHANGED
|
@@ -242,13 +242,12 @@ describe("useQuery", () => {
|
|
|
242
242
|
renderHook(
|
|
243
243
|
(props) =>
|
|
244
244
|
useQuery({
|
|
245
|
-
queryFn:
|
|
245
|
+
queryFn: resolver,
|
|
246
246
|
key: props.key,
|
|
247
247
|
}),
|
|
248
248
|
{
|
|
249
249
|
initialProps: {
|
|
250
250
|
key: ["dummyQK1"],
|
|
251
|
-
resolver,
|
|
252
251
|
},
|
|
253
252
|
}
|
|
254
253
|
)
|
|
@@ -257,9 +256,9 @@ describe("useQuery", () => {
|
|
|
257
256
|
await waitFor(() => until(result.current.status === "success"), {
|
|
258
257
|
timeout: 1000,
|
|
259
258
|
});
|
|
260
|
-
rerender({ key: ["dummyQK2"]
|
|
261
|
-
rerender({ key: ["
|
|
262
|
-
expect(resolver).toBeCalledTimes(
|
|
259
|
+
rerender({ key: ["dummyQK2"] });
|
|
260
|
+
rerender({ key: ["dummyQK3"] });
|
|
261
|
+
expect(resolver).toBeCalledTimes(3);
|
|
263
262
|
});
|
|
264
263
|
test("stateless mode", async () => {
|
|
265
264
|
const resolver = vi.fn(() => defaultQueryMock.getMockedQueryFn());
|
package/src/useQuery.ts
CHANGED
|
@@ -9,10 +9,10 @@ export interface QueryOptions<
|
|
|
9
9
|
> {
|
|
10
10
|
queryFn: (params: TParams | undefined | void) => Promise<TData>;
|
|
11
11
|
onSuccess?: (data: TData) => Promise<void> | void;
|
|
12
|
-
onError?: (error: TError) => Promise<void> | void;
|
|
12
|
+
onError?: (error: TQueryError<TError>) => Promise<void> | void;
|
|
13
13
|
onSettled?: (
|
|
14
14
|
data: TData | undefined,
|
|
15
|
-
error: TError | undefined
|
|
15
|
+
error: TQueryError<TError> | undefined
|
|
16
16
|
) => Promise<void> | void;
|
|
17
17
|
timeout?: number;
|
|
18
18
|
enabled?: boolean;
|
|
@@ -32,6 +32,8 @@ type RacePromises<T> = Promise<T | RaceErrorReason>;
|
|
|
32
32
|
type WithData<TData> = { data: TData | undefined };
|
|
33
33
|
type TQueryKey = readonly any[];
|
|
34
34
|
|
|
35
|
+
type TQueryError<TError> = TError | Error | null;
|
|
36
|
+
|
|
35
37
|
export type UseQueryResult<
|
|
36
38
|
TData = unknown,
|
|
37
39
|
TError = unknown,
|
|
@@ -39,7 +41,7 @@ export type UseQueryResult<
|
|
|
39
41
|
TSelectData = TData,
|
|
40
42
|
Stateless extends true | undefined = undefined,
|
|
41
43
|
> = {
|
|
42
|
-
error: TError
|
|
44
|
+
error: TQueryError<TError>;
|
|
43
45
|
status: QueryStatus;
|
|
44
46
|
refetch: (params: TParams | void | undefined) => Promise<void>;
|
|
45
47
|
reset: () => void;
|
|
@@ -51,11 +53,11 @@ function normalizeQueryKey(queryKey: TQueryKey): string {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
type TInternalCallbackRef<TData, TError, TSelectData> = {
|
|
54
|
-
onError?: (error: TError) => Promise<void> | void;
|
|
56
|
+
onError?: (error: TQueryError<TError>) => Promise<void> | void;
|
|
55
57
|
onSuccess?: (data: TData) => Promise<void> | void;
|
|
56
58
|
onSettled?: (
|
|
57
59
|
data: TData | undefined,
|
|
58
|
-
error: TError | undefined
|
|
60
|
+
error: TQueryError<TError> | undefined
|
|
59
61
|
) => Promise<void> | void;
|
|
60
62
|
compareOrThrow?: (data: TData) => boolean;
|
|
61
63
|
select?: (data: TData) => TSelectData;
|
|
@@ -70,9 +72,11 @@ type TInternalOptionsRef<TData> = {
|
|
|
70
72
|
stateless?: boolean;
|
|
71
73
|
};
|
|
72
74
|
|
|
75
|
+
const getRandomUUID = () => Math.random().toString(36).substring(2, 15);
|
|
76
|
+
|
|
73
77
|
export const useQuery = <
|
|
74
78
|
TData = unknown,
|
|
75
|
-
TError = unknown
|
|
79
|
+
TError = TQueryError<unknown>,
|
|
76
80
|
TParams = void,
|
|
77
81
|
TSelected = TData,
|
|
78
82
|
Stateless extends true | undefined = undefined,
|
|
@@ -101,7 +105,6 @@ export const useQuery = <
|
|
|
101
105
|
key: queryKeyPassed,
|
|
102
106
|
} = options;
|
|
103
107
|
const queryKey = normalizeQueryKey(queryKeyPassed ?? []);
|
|
104
|
-
const promiseResolutionInProgress = useRef(false);
|
|
105
108
|
const lastFetchedAt = useRef<number | null>(null);
|
|
106
109
|
const lastQueryKey = useRef<string | undefined>(undefined);
|
|
107
110
|
const queryFnRef = useRef(queryFn);
|
|
@@ -132,11 +135,12 @@ export const useQuery = <
|
|
|
132
135
|
stateless,
|
|
133
136
|
timeout,
|
|
134
137
|
};
|
|
138
|
+
const requestIdRef = useRef(getRandomUUID());
|
|
135
139
|
|
|
136
140
|
const [status, setStatus] = useState<QueryStatus>(() => {
|
|
137
141
|
return "idle";
|
|
138
142
|
});
|
|
139
|
-
const [error, setError] = useState<TError
|
|
143
|
+
const [error, setError] = useState<TQueryError<TError>>(null);
|
|
140
144
|
const [data, setData] = useState<TData | undefined>(() => {
|
|
141
145
|
if (initialData !== undefined) {
|
|
142
146
|
if (typeof initialData === "function") {
|
|
@@ -174,10 +178,9 @@ export const useQuery = <
|
|
|
174
178
|
|
|
175
179
|
const refetch = useCallback(
|
|
176
180
|
async (params: TParams | void | undefined = undefined) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) {
|
|
181
|
+
const requestId = getRandomUUID();
|
|
182
|
+
requestIdRef.current = requestId;
|
|
183
|
+
if (!internalOptionsRef.current.enabled) {
|
|
181
184
|
return;
|
|
182
185
|
}
|
|
183
186
|
|
|
@@ -199,9 +202,10 @@ export const useQuery = <
|
|
|
199
202
|
: []),
|
|
200
203
|
];
|
|
201
204
|
|
|
202
|
-
promiseResolutionInProgress.current = true;
|
|
203
205
|
const res = await Promise.race(promises);
|
|
204
|
-
|
|
206
|
+
if (requestIdRef.current !== requestId) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
205
209
|
|
|
206
210
|
if (res === "timeout") {
|
|
207
211
|
throw new Error("timeout");
|
|
@@ -213,7 +217,7 @@ export const useQuery = <
|
|
|
213
217
|
internalCallbackRef.current.compareOrThrow &&
|
|
214
218
|
internalCallbackRef.current.compareOrThrow(res) === true
|
|
215
219
|
) {
|
|
216
|
-
throw new Error("
|
|
220
|
+
throw new Error("failedComparison");
|
|
217
221
|
}
|
|
218
222
|
|
|
219
223
|
setStatus("success");
|
|
@@ -238,42 +242,23 @@ export const useQuery = <
|
|
|
238
242
|
}, [refetch]);
|
|
239
243
|
|
|
240
244
|
useEffect(() => {
|
|
241
|
-
|
|
242
|
-
refetch();
|
|
243
|
-
}
|
|
244
|
-
}, [enabled, refetch, queryKey]);
|
|
245
|
-
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
if (lastQueryKey.current === undefined) {
|
|
248
|
-
lastQueryKey.current = queryKey;
|
|
249
|
-
refetch();
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (queryKey !== lastQueryKey.current) {
|
|
254
|
-
lastQueryKey.current = queryKey;
|
|
255
|
-
refetch();
|
|
256
|
-
}
|
|
257
|
-
}, [queryKey, refetch, enabled]);
|
|
245
|
+
const cleanups: (() => void)[] = [];
|
|
258
246
|
|
|
259
|
-
|
|
247
|
+
// If the query should be refetched on window focus, add a listener
|
|
260
248
|
if (refetchOnWindowFocus) {
|
|
261
249
|
const queryHandleFocus = () => {
|
|
262
|
-
|
|
263
|
-
refetch();
|
|
264
|
-
}
|
|
250
|
+
refetch();
|
|
265
251
|
};
|
|
266
252
|
|
|
267
253
|
window.addEventListener("focus", queryHandleFocus);
|
|
268
254
|
|
|
269
|
-
|
|
255
|
+
cleanups.push(() => {
|
|
270
256
|
window.removeEventListener("focus", queryHandleFocus);
|
|
271
|
-
};
|
|
257
|
+
});
|
|
272
258
|
}
|
|
273
|
-
}, [refetchOnWindowFocus, refetch, shouldRefetch]);
|
|
274
259
|
|
|
275
|
-
|
|
276
|
-
if (refetchInterval
|
|
260
|
+
// If the query should be refetched on an interval, add an interval
|
|
261
|
+
if (refetchInterval > 0) {
|
|
277
262
|
const interval = setInterval(
|
|
278
263
|
() => {
|
|
279
264
|
if (shouldRefetch()) {
|
|
@@ -283,11 +268,31 @@ export const useQuery = <
|
|
|
283
268
|
Math.max(refetchInterval, 1000)
|
|
284
269
|
);
|
|
285
270
|
|
|
286
|
-
|
|
271
|
+
cleanups.push(() => {
|
|
287
272
|
clearInterval(interval);
|
|
288
|
-
};
|
|
273
|
+
});
|
|
289
274
|
}
|
|
290
|
-
|
|
275
|
+
|
|
276
|
+
// If the query key has changed, refetch
|
|
277
|
+
if (
|
|
278
|
+
lastQueryKey.current === undefined ||
|
|
279
|
+
queryKey !== lastQueryKey.current
|
|
280
|
+
) {
|
|
281
|
+
lastQueryKey.current = queryKey;
|
|
282
|
+
refetch();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return () => {
|
|
286
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
287
|
+
};
|
|
288
|
+
}, [
|
|
289
|
+
queryKey,
|
|
290
|
+
enabled,
|
|
291
|
+
refetchOnWindowFocus,
|
|
292
|
+
refetchInterval,
|
|
293
|
+
shouldRefetch,
|
|
294
|
+
refetch,
|
|
295
|
+
]);
|
|
291
296
|
|
|
292
297
|
return {
|
|
293
298
|
data:
|