Skip to content

Commit 1f74353

Browse files
authored
feat(useQuery, useInfiniteQuery): deep watch for reactive arguments, deep unref options (#111)
* refactor: use lodash clonedeepwith function * refactor: improve useQuery logic * fix: typings * refactor: cleanup * refactor: cleanup * refactor: cleanup * refactor: rename getQueryOptions function * test: add more tests * revert: bring back parseQueryArgs * refactor: replace lodash clonedeep with custom implementation * chore: cleanup * fix: typescript issue
1 parent d1a5882 commit 1f74353

File tree

8 files changed

+315
-119
lines changed

8 files changed

+315
-119
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"@vue/composition-api": "^1.2.4",
7070
"@vue/eslint-config-prettier": "^6.0.0",
7171
"@vue/eslint-config-typescript": "^8.0.0",
72+
"@vue/vue3-jest": "27.0.0-alpha.1",
7273
"eslint": "^8.1.0",
7374
"eslint-config-prettier": "^8.3.0",
7475
"eslint-plugin-prettier": "^4.0.0",
@@ -85,7 +86,6 @@
8586
"ts-node": "^10.4.0",
8687
"typescript": "^4.4.4",
8788
"vue": "^3.2.20",
88-
"@vue/vue3-jest": "27.0.0-alpha.1",
8989
"vue2": "npm:vue@2"
9090
}
9191
}

src/vue/__tests__/useQuery.test.ts

+44-11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
flushPromises,
1212
rejectFetcher,
1313
simpleFetcher,
14+
getSimpleFetcherWithReturnData,
1415
noop,
1516
} from "./test-utils";
1617
import { useQuery } from "../useQuery";
@@ -31,14 +32,9 @@ describe("useQuery", () => {
3132
test("should properly execute query", () => {
3233
useQuery("key0", simpleFetcher, { staleTime: 1000 });
3334

34-
expect(useBaseQuery).toBeCalledWith(
35-
{
36-
queryFn: expect.anything(),
37-
queryKey: "key0",
38-
staleTime: 1000,
39-
},
40-
QueryObserver
41-
);
35+
expect(useBaseQuery).toBeCalledWith(QueryObserver, "key0", simpleFetcher, {
36+
staleTime: 1000,
37+
});
4238
});
4339

4440
test("should return loading status initially", () => {
@@ -52,14 +48,51 @@ describe("useQuery", () => {
5248
});
5349
});
5450

55-
test("should resolve to success and update reactive state", async () => {
56-
const query = useQuery("key2", simpleFetcher);
51+
test("should resolve to success and update reactive state: useQuery(key, dataFn)", async () => {
52+
const query = useQuery("key2", getSimpleFetcherWithReturnData("result2"));
53+
54+
await flushPromises();
55+
56+
expect(query).toMatchObject({
57+
status: { value: "success" },
58+
data: { value: "result2" },
59+
isLoading: { value: false },
60+
isFetching: { value: false },
61+
isFetched: { value: true },
62+
isSuccess: { value: true },
63+
});
64+
});
65+
66+
test("should resolve to success and update reactive state: useQuery(optionsObj)", async () => {
67+
const query = useQuery({
68+
queryKey: "key31",
69+
queryFn: getSimpleFetcherWithReturnData("result31"),
70+
enabled: true,
71+
});
72+
73+
await flushPromises();
74+
75+
expect(query).toMatchObject({
76+
status: { value: "success" },
77+
data: { value: "result31" },
78+
isLoading: { value: false },
79+
isFetching: { value: false },
80+
isFetched: { value: true },
81+
isSuccess: { value: true },
82+
});
83+
});
84+
85+
test("should resolve to success and update reactive state: useQuery(key, optionsObj)", async () => {
86+
const query = useQuery("key32", {
87+
queryFn: getSimpleFetcherWithReturnData("result32"),
88+
enabled: true,
89+
});
5790

5891
await flushPromises();
5992

6093
expect(query).toMatchObject({
6194
status: { value: "success" },
62-
data: { value: "Some data" },
95+
data: { value: "result32" },
6396
isLoading: { value: false },
6497
isFetching: { value: false },
6598
isFetched: { value: true },

src/vue/__tests__/utils.test.ts

+114
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
parseMutationFilterArgs,
77
parseQueryArgs,
88
updateState,
9+
cloneDeep,
10+
cloneDeepUnref,
911
} from "../utils";
12+
import { reactive, ref } from "vue-demi";
1013

1114
describe("utils", () => {
1215
describe("isQueryKey", () => {
@@ -147,4 +150,115 @@ describe("utils", () => {
147150
expect(origin).toEqual(expected);
148151
});
149152
});
153+
154+
describe("cloneDeep", () => {
155+
test("should copy primitives and functions AS-IS", () => {
156+
expect(cloneDeep(3456)).toBe(3456);
157+
expect(cloneDeep("theString")).toBe("theString");
158+
expect(cloneDeep(null)).toBe(null);
159+
});
160+
161+
test("should copy Maps and Sets AS-IS", () => {
162+
const setVal = new Set([3, 4, 5]);
163+
const setValCopy = cloneDeep(setVal);
164+
expect(setValCopy).toBe(setVal);
165+
expect(setValCopy).toStrictEqual(new Set([3, 4, 5]));
166+
167+
const mapVal = new Map([
168+
["a", "aVal"],
169+
["b", "bVal"],
170+
]);
171+
const mapValCopy = cloneDeep(mapVal);
172+
expect(mapValCopy).toBe(mapVal);
173+
expect(mapValCopy).toStrictEqual(
174+
new Map([
175+
["a", "aVal"],
176+
["b", "bVal"],
177+
])
178+
);
179+
});
180+
181+
test("should deeply copy arrays", () => {
182+
const val = [
183+
25,
184+
"str",
185+
null,
186+
new Set([3, 4]),
187+
[5, 6, { a: 1 }],
188+
undefined,
189+
];
190+
const cp = cloneDeep(val);
191+
expect(cp).toStrictEqual([
192+
25,
193+
"str",
194+
null,
195+
new Set([3, 4]),
196+
[5, 6, { a: 1 }],
197+
undefined,
198+
]);
199+
expect(cp).not.toBe(val);
200+
expect(cp[3]).toBe(val[3]); // Set([3, 4])
201+
expect(cp[4]).not.toBe(val[4]); // [5, 6, { a: 1 }]
202+
expect((cp[4] as number[])[2]).not.toBe((val[4] as number[])[2]); // { a : 1 }
203+
});
204+
205+
test("should deeply copy object", () => {
206+
const val = reactive({
207+
a: 25,
208+
b: "str",
209+
c: null,
210+
d: undefined,
211+
e: new Set([5, 6]),
212+
f: [3, 4],
213+
g: { fa: 26 },
214+
});
215+
const cp = cloneDeep(val);
216+
217+
expect(cp).toStrictEqual({
218+
a: 25,
219+
b: "str",
220+
c: null,
221+
d: undefined,
222+
e: new Set([5, 6]),
223+
f: [3, 4],
224+
g: { fa: 26 },
225+
});
226+
227+
expect(cp.e).toBe(val.e); // Set
228+
expect(cp.f).not.toBe(val.f); // []
229+
expect(cp.g).not.toBe(val.g); // {}
230+
});
231+
});
232+
233+
describe("cloneDeepUnref", () => {
234+
test("should unref primitives", () => {
235+
expect(cloneDeepUnref(ref(34))).toBe(34);
236+
expect(cloneDeepUnref(ref("mystr"))).toBe("mystr");
237+
});
238+
239+
test("should deeply unref arrays", () => {
240+
const val = ref([2, 3, ref(4), ref("5"), { a: ref(6) }, [ref(7)]]);
241+
const cp = cloneDeepUnref(val);
242+
expect(cp).toStrictEqual([2, 3, 4, "5", { a: 6 }, [7]]);
243+
});
244+
245+
test("should deeply unref objects", () => {
246+
const val = ref({
247+
a: 1,
248+
b: ref(2),
249+
c: [ref("c1"), ref(["c2"])],
250+
d: {
251+
e: ref("e"),
252+
},
253+
});
254+
const cp = cloneDeepUnref(val);
255+
256+
expect(cp).toEqual({
257+
a: 1,
258+
b: 2,
259+
c: ["c1", ["c2"]],
260+
d: { e: "e" },
261+
});
262+
});
263+
});
150264
});

src/vue/types.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { QueryKey, QueryObserverOptions } from "react-query/types/core";
1+
import type {
2+
QueryKey,
3+
QueryObserverOptions,
4+
InfiniteQueryObserverOptions,
5+
} from "react-query/types/core";
26
import { Ref } from "vue-demi";
37

48
export type MaybeRef<T> = Ref<T> | T;
@@ -30,3 +34,29 @@ export type VueQueryObserverOptions<
3034
>[Property]
3135
>;
3236
};
37+
38+
// A Vue version of InfiniteQueryObserverOptions from "react-query/types/core"
39+
// Accept refs as options
40+
export type VueInfiniteQueryObserverOptions<
41+
TQueryFnData = unknown,
42+
TError = unknown,
43+
TData = unknown,
44+
TQueryData = unknown,
45+
TQueryKey extends QueryKey = QueryKey
46+
> = {
47+
[Property in keyof InfiniteQueryObserverOptions<
48+
TQueryFnData,
49+
TError,
50+
TData,
51+
TQueryData,
52+
TQueryKey
53+
>]: MaybeRef<
54+
InfiniteQueryObserverOptions<
55+
TQueryFnData,
56+
TError,
57+
TData,
58+
TQueryData,
59+
TQueryKey
60+
>[Property]
61+
>;
62+
};

0 commit comments

Comments
 (0)