Skip to content

Commit 972b8bf

Browse files
committed
momoize query key
1 parent da404fd commit 972b8bf

11 files changed

+23284
-103
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
"**/temp": false,
2121
"**/node_modules": false
2222
},
23-
"workspace.isHidden": false
23+
"workspace.isHidden": false,
24+
"typescript.tsdk": "node_modules/typescript/lib"
2425
}

src/hooks/appQueryKeyBuilder.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
import { AppQueryOptions } from ".";
33
import { AppRoutes } from "..";
44

5-
export function appQueryKeyBuilder<Scope extends keyof AppRoutes = "main", T extends keyof AppRoutes[Scope] = keyof AppRoutes[Scope]>
6-
(routeOrRouteObj: T | { scope: Scope, route: T },
7-
appQueryOptions: Partial<AppQueryOptions> = {})
8-
: any {
9-
10-
const keyForUseQuery: any[] = [routeOrRouteObj];
11-
12-
const { extraRoutePath, query, pathParams } = appQueryOptions
13-
if (extraRoutePath) {
14-
if (typeof extraRoutePath === "object") {
15-
keyForUseQuery.push([...extraRoutePath])
16-
}
17-
else {
18-
keyForUseQuery.push(extraRoutePath)
19-
}
20-
}
21-
if (pathParams) {
22-
keyForUseQuery.push(pathParams)
23-
}
24-
if (query) {
25-
const itemToPush = typeof query === "string" ? query : { ...query }
26-
keyForUseQuery.push(itemToPush)
27-
}
28-
return keyForUseQuery;
5+
export function appQueryKeyBuilder<
6+
Scope extends keyof AppRoutes = "main",
7+
T extends keyof AppRoutes[Scope] = keyof AppRoutes[Scope]
8+
>(
9+
routeOrRouteObj: T | { scope: Scope; route: T },
10+
appQueryOptions: Partial<AppQueryOptions> = {}
11+
): any {
12+
const keyForUseQuery: any[] = [routeOrRouteObj];
2913

14+
const { extraRoutePath, query, pathParams } = appQueryOptions;
15+
if (extraRoutePath) {
16+
if (typeof extraRoutePath === "object") {
17+
keyForUseQuery.push([...extraRoutePath]);
18+
} else {
19+
keyForUseQuery.push(extraRoutePath);
20+
}
21+
}
22+
if (pathParams) {
23+
keyForUseQuery.push(pathParams);
24+
}
25+
if (query) {
26+
const itemToPush = typeof query === "string" ? query : { ...query };
27+
keyForUseQuery.push(itemToPush);
28+
}
29+
return keyForUseQuery;
3030
}

src/hooks/useAppMutation.ts

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,94 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { MutationKey, useMutation, UseMutationOptions, UseMutationResult, useQueryClient } from "react-query";
2+
import {
3+
MutationKey,
4+
useMutation,
5+
UseMutationOptions,
6+
UseMutationResult,
7+
useQueryClient,
8+
} from "react-query";
39
import { DeepPartial } from "src/helpers/typeUtils";
410
import { AppQueryOptions } from ".";
5-
import { AppRoutes, ApiPayloadType, ApiResponseType, ApiRoute } from "..";
11+
import { ApiPayloadType, ApiResponseType, ApiRoute, AppRoutes } from "..";
612
import { httpPost } from "../imperative";
7-
import { appQueryKeyBuilder } from "./appQueryKeyBuilder";
13+
import { useQueryKeyBuilder } from "./useQueryKeyBuilder";
814
/**
9-
*
15+
*
1016
* @param routeOrRouteObj a route from AppRoutes or an object with a scope and a route
11-
* @param appQueryOptions http options for the query. Path params is needed to replace path variables
17+
* @param appQueryOptions http options for the query. Path params is needed to replace path variables
1218
* @param mutationOptions react-query's mutation options
13-
* @returns
19+
* @returns
1420
*/
15-
export function useAppMutation<Scope extends keyof AppRoutes = "main", Route extends ApiRoute<Scope> = ApiRoute<Scope>>
16-
(routeOrRouteObj: Route | { scope: Scope, route: Route }, appQueryOptions: Partial<Omit<AppQueryOptions<ApiPayloadType<Scope, Route>>, "apiScope">> = {},
17-
mutationOptions: UseMutationOptions = {}
18-
): UseMutationResult<ApiResponseType<Scope, Route, "mutation">, any, DeepPartial<(ApiPayloadType<Scope, Route> & { _pathParams?: { [key: string]: any } })>, any> {
19-
20-
const queryClient = useQueryClient();
21-
const keyForUseQuery = appQueryKeyBuilder(routeOrRouteObj, appQueryOptions);// any = [route, typeof queryOptions.query === "string" ? queryOptions.query : { ...queryOptions.query }];
22-
23-
24-
/**
25-
* _pathParams is used to replace the url path variables.
26-
* It has been put here because by default react query allows you to add extra data to mutateFn only when you call useMutation
27-
* and not when you call mutate or mutateAsync.
28-
* Sometimes may be tricky to have pathParams at the moment you call useMutation, so this has been added here for conveniece.
29-
* It has been prefixed with underscore here (and no in useAppMutation) to allow the case in which the final user needs to send a property called pathParams
30-
* in the request payload.
31-
* If you need to send a post request with a _pathParams property, it will not work
32-
*/
33-
return useMutation<ApiResponseType<Scope, Route, "mutation">, any, DeepPartial<(ApiPayloadType<Scope, Route> & { _pathParams?: { [key: string]: any } })>, any>(keyForUseQuery as MutationKey, ({ _pathParams, ...params }: any) => {
34-
console.log("options", appQueryOptions)
35-
console.log("params", params, _pathParams)
36-
// const finalRoute = (routeOrRouteObj as string).split("/")
37-
// .map((part) => {
38-
// if (part.startsWith(":")) {
39-
// const finalPart = part.substring(1);
40-
// const pathParam = _pathParams?.[finalPart] ?? appQueryOptions.pathParams?.[finalPart];
41-
// if (!pathParam) {
42-
// console.warn("you are missing a path param for route", routeOrRouteObj)
43-
// return undefined;
44-
// }
45-
// return pathParam
46-
// }
47-
// return part;
48-
// })
49-
// .join("/");
50-
// const { pathParams, ...restOfAppQueryOptions } = appQueryOptions;
51-
return httpPost(routeOrRouteObj as any, { payload: params, ...appQueryOptions, pathParams: _pathParams ?? appQueryOptions.pathParams }) as Promise<ApiResponseType<Scope, Route, "mutation">>
52-
53-
}, {
54-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
55-
//@ts-ignore
56-
onSuccess: (data: any, variables: any, context: any) => {
57-
queryClient.invalidateQueries({ queryKey: routeOrRouteObj as any });
58-
mutationOptions.onSuccess?.(data, variables, context)
59-
// return true;
60-
},
61-
...mutationOptions,
62-
});
63-
21+
export function useAppMutation<
22+
Scope extends keyof AppRoutes = "main",
23+
Route extends ApiRoute<Scope> = ApiRoute<Scope>
24+
>(
25+
routeOrRouteObj: Route | { scope: Scope; route: Route },
26+
appQueryOptions: Partial<
27+
Omit<AppQueryOptions<ApiPayloadType<Scope, Route>>, "apiScope">
28+
> = {},
29+
mutationOptions: UseMutationOptions = {}
30+
): UseMutationResult<
31+
ApiResponseType<Scope, Route, "mutation">,
32+
any,
33+
DeepPartial<
34+
ApiPayloadType<Scope, Route> & { _pathParams?: { [key: string]: any } }
35+
>,
36+
any
37+
> {
38+
const queryClient = useQueryClient();
39+
const keyForUseQuery = useQueryKeyBuilder(routeOrRouteObj, appQueryOptions); // any = [route, typeof queryOptions.query === "string" ? queryOptions.query : { ...queryOptions.query }];
6440

41+
/**
42+
* _pathParams is used to replace the url path variables.
43+
* It has been put here because by default react query allows you to add extra data to mutateFn only when you call useMutation
44+
* and not when you call mutate or mutateAsync.
45+
* Sometimes may be tricky to have pathParams at the moment you call useMutation, so this has been added here for conveniece.
46+
* It has been prefixed with underscore here (and no in useAppMutation) to allow the case in which the final user needs to send a property called pathParams
47+
* in the request payload.
48+
* If you need to send a post request with a _pathParams property, it will not work
49+
*/
50+
return useMutation<
51+
ApiResponseType<Scope, Route, "mutation">,
52+
any,
53+
DeepPartial<
54+
ApiPayloadType<Scope, Route> & { _pathParams?: { [key: string]: any } }
55+
>,
56+
any
57+
>(
58+
keyForUseQuery as MutationKey,
59+
({ _pathParams, ...params }: any) => {
60+
console.log("options", appQueryOptions);
61+
console.log("params", params, _pathParams);
62+
// const finalRoute = (routeOrRouteObj as string).split("/")
63+
// .map((part) => {
64+
// if (part.startsWith(":")) {
65+
// const finalPart = part.substring(1);
66+
// const pathParam = _pathParams?.[finalPart] ?? appQueryOptions.pathParams?.[finalPart];
67+
// if (!pathParam) {
68+
// console.warn("you are missing a path param for route", routeOrRouteObj)
69+
// return undefined;
70+
// }
71+
// return pathParam
72+
// }
73+
// return part;
74+
// })
75+
// .join("/");
76+
// const { pathParams, ...restOfAppQueryOptions } = appQueryOptions;
77+
return httpPost(routeOrRouteObj as any, {
78+
payload: params,
79+
...appQueryOptions,
80+
pathParams: _pathParams ?? appQueryOptions.pathParams,
81+
}) as Promise<ApiResponseType<Scope, Route, "mutation">>;
82+
},
83+
{
84+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
85+
//@ts-ignore
86+
onSuccess: (data: any, variables: any, context: any) => {
87+
queryClient.invalidateQueries({ queryKey: routeOrRouteObj as any });
88+
mutationOptions.onSuccess?.(data, variables, context);
89+
// return true;
90+
},
91+
...mutationOptions,
92+
}
93+
);
6594
}
66-

src/hooks/useAppQuery.spec.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,20 @@ describe("basic usage", () => {
147147
});
148148
});
149149

150-
test("typings are correct", () => {
150+
test("typings are correct", async () => {
151151
useQueryMock = jest.spyOn(rq, "useQuery").mockImplementation(jest.fn());
152-
act(() => {
152+
await waitForHook(() => {
153153
const query = useAppQuery("fake-object");
154154
expectType<rq.UseQueryResult<DefaultGetManyResponse<MyObject>, unknown>>(
155155
query
156156
);
157157
});
158158

159159
useQueryMock.mockClear();
160-
act(() => {
160+
161+
await waitForHook(() => {
161162
const query = useAppQuery("fake-object/:id");
162163
expectType<rq.UseQueryResult<MyObject, unknown>>(query);
163164
});
164165
});
165166
});
166-

src/hooks/useAppQuery.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { useQuery, UseQueryOptions, UseQueryResult } from "react-query";
33
import { AppQueryOptions } from ".";
4-
import { AppRoutes, ApiResponseType, ApiRoute } from "..";
5-
import { appQueryKeyBuilder } from "./appQueryKeyBuilder";
6-
import { httpGet } from "../imperative"
4+
import { ApiResponseType, ApiRoute, AppRoutes } from "..";
5+
import { httpGet } from "../imperative";
6+
import { useQueryKeyBuilder } from "./useQueryKeyBuilder";
77

88
/**
99
* Alias for array
1010
*
1111
* @typeParam Scope - One of the keys of {@link AppRoutes}
1212
*/
13-
export function useAppQuery<Scope extends keyof AppRoutes = "main", Route extends ApiRoute<Scope> = ApiRoute<Scope>>
14-
(routeOrRouteObj: Route | { scope: Scope, route: Route },
15-
appQueryOptions: Partial<Omit<AppQueryOptions, "payload" | "apiScope">> = {},
16-
useQueryOptions: UseQueryOptions = {})
17-
: UseQueryResult<ApiResponseType<Scope, Route>> {
18-
19-
const keyForUseQuery = appQueryKeyBuilder(routeOrRouteObj, appQueryOptions);
20-
21-
type RES = ApiResponseType<Scope, Route>
22-
return useQuery<RES>(keyForUseQuery, (params: any) => {
23-
return httpGet(routeOrRouteObj, { ...appQueryOptions }) as Promise<RES>
24-
}, useQueryOptions as any);
13+
export function useAppQuery<
14+
Scope extends keyof AppRoutes = "main",
15+
Route extends ApiRoute<Scope> = ApiRoute<Scope>
16+
>(
17+
routeOrRouteObj: Route | { scope: Scope; route: Route },
18+
appQueryOptions: Partial<Omit<AppQueryOptions, "payload" | "apiScope">> = {},
19+
useQueryOptions: UseQueryOptions = {}
20+
): UseQueryResult<ApiResponseType<Scope, Route>> {
21+
const keyForUseQuery = useQueryKeyBuilder(routeOrRouteObj, appQueryOptions);
2522

23+
type RES = ApiResponseType<Scope, Route>;
24+
return useQuery<RES>(
25+
keyForUseQuery,
26+
(params: any) => {
27+
return httpGet(routeOrRouteObj, { ...appQueryOptions }) as Promise<RES>;
28+
},
29+
useQueryOptions as any
30+
);
2631
}

src/hooks/useQueryKeyBuilder.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { useMemo } from "react";
3+
import { AppQueryOptions } from ".";
4+
import { AppRoutes } from "..";
5+
import { appQueryKeyBuilder } from "./appQueryKeyBuilder";
6+
7+
export function useQueryKeyBuilder<
8+
Scope extends keyof AppRoutes = "main",
9+
T extends keyof AppRoutes[Scope] = keyof AppRoutes[Scope]
10+
>(
11+
routeOrRouteObj: T | { scope: Scope; route: T },
12+
appQueryOptions: Partial<AppQueryOptions> = {}
13+
): any {
14+
const result = useMemo(() => {
15+
return appQueryKeyBuilder(routeOrRouteObj, appQueryOptions);
16+
}, [appQueryOptions, routeOrRouteObj]);
17+
18+
return result;
19+
}

0 commit comments

Comments
 (0)