Skip to content

Commit bbd6328

Browse files
committed
feat(react-client): add memoization for the useMutation & useQuery* hooks
1 parent 6842723 commit bbd6328

2 files changed

Lines changed: 123 additions & 72 deletions

File tree

packages/react-client/src/callbacks/useMutation.ts

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import type {
44
OperationSchema,
55
ServiceOperationMutationKey,
6-
ServiceOperationUseMutation,
6+
ServiceOperationUseMutationOptions,
77
} from '@openapi-qraft/tanstack-query-react-types';
88
import type { DefaultError, UseMutationResult } from '@tanstack/react-query';
99
import type { CreateAPIQueryClientOptions } from '../qraftAPIClient.js';
1010
import { useMutation as useMutationBase } from '@tanstack/react-query';
11+
import { useMemo } from 'react';
1112
import { composeMutationKey } from '../lib/composeMutationKey.js';
1213
import { requestFnResponseRejecter } from '../lib/requestFnResponseRejecter.js';
1314
import { requestFnResponseResolver } from '../lib/requestFnResponseResolver.js';
@@ -20,68 +21,91 @@ export const useMutation: <
2021
>(
2122
qraftOptions: CreateAPIQueryClientOptions,
2223
schema: OperationSchema,
23-
args: Parameters<
24-
ServiceOperationUseMutation<
25-
OperationSchema,
26-
object | undefined,
27-
TVariables,
28-
TData,
29-
DefaultError
30-
>['useMutation']
31-
>
24+
args: [
25+
parameters?: unknown,
26+
options?: ServiceOperationUseMutationOptions<any, any, any, any, any>,
27+
]
3228
) => UseMutationResult<TData, TError, TVariables, TContext> = (
3329
qraftOptions,
3430
schema,
3531
args
3632
) => {
3733
const [parameters, options] = args;
3834

39-
if (
40-
parameters &&
41-
typeof parameters === 'object' &&
42-
options &&
43-
'mutationKey' in options
44-
)
45-
throw new Error(
46-
`'useMutation': parameters and 'options.mutationKey' cannot be used together`
47-
);
48-
49-
const mutationKey =
35+
const optionsMutationKey =
5036
options && 'mutationKey' in options
5137
? (options.mutationKey as ServiceOperationMutationKey<
5238
typeof schema,
5339
unknown
5440
>)
55-
: composeMutationKey(schema, parameters);
41+
: undefined;
42+
43+
if (parameters && typeof parameters === 'object' && optionsMutationKey)
44+
throw new Error(
45+
`'useMutation': parameters and 'options.mutationKey' cannot be used together`
46+
);
47+
48+
const mutationKey = useMemo(
49+
() => optionsMutationKey ?? composeMutationKey(schema, parameters),
50+
[optionsMutationKey, schema, parameters]
51+
);
5652

5753
return useMutationBase(
5854
{
5955
...options,
6056
mutationKey,
61-
mutationFn:
62-
options?.mutationFn ??
63-
(parameters
64-
? function (bodyPayload) {
65-
return qraftOptions
66-
.requestFn(schema, {
67-
parameters,
68-
baseUrl: qraftOptions.baseUrl,
69-
body: bodyPayload as never,
70-
})
71-
.then(requestFnResponseResolver, requestFnResponseRejecter);
72-
}
73-
: function (parametersAndBodyPayload) {
74-
const { body, ...parameters } = parametersAndBodyPayload ?? {};
75-
76-
return qraftOptions
77-
.requestFn(schema, {
78-
parameters,
79-
baseUrl: qraftOptions.baseUrl,
80-
body,
81-
} as never)
82-
.then(requestFnResponseResolver, requestFnResponseRejecter);
83-
}),
57+
mutationFn: useMemo(
58+
() =>
59+
options?.mutationFn ??
60+
qraftMutationFn.bind(
61+
null,
62+
qraftOptions.requestFn,
63+
qraftOptions.baseUrl,
64+
schema,
65+
parameters
66+
),
67+
[
68+
options?.mutationFn,
69+
qraftOptions.requestFn,
70+
qraftOptions.baseUrl,
71+
schema,
72+
parameters,
73+
]
74+
),
8475
},
8576
qraftOptions.queryClient
8677
) as never;
8778
};
79+
80+
function qraftMutationFn(
81+
requestFn: CreateAPIQueryClientOptions['requestFn'],
82+
baseUrl: CreateAPIQueryClientOptions['baseUrl'],
83+
schema: OperationSchema,
84+
parameters: unknown,
85+
mutationVariables:
86+
| {
87+
body: unknown;
88+
}
89+
| undefined
90+
) {
91+
if (parameters) {
92+
return requestFn(schema, {
93+
parameters,
94+
baseUrl,
95+
body: mutationVariables as never,
96+
}).then(requestFnResponseResolver, requestFnResponseRejecter);
97+
}
98+
99+
const mutationParameters =
100+
typeof mutationVariables === 'object' && mutationVariables !== null
101+
? { ...mutationVariables }
102+
: undefined;
103+
const body = mutationParameters?.body;
104+
delete mutationParameters?.body;
105+
106+
return requestFn(schema, {
107+
parameters: mutationParameters,
108+
baseUrl,
109+
body,
110+
} as never).then(requestFnResponseResolver, requestFnResponseRejecter);
111+
}

packages/react-client/src/lib/useComposeUseQueryOptions.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
'use client';
22

3-
import type { ServiceOperationQueryKey } from '@openapi-qraft/tanstack-query-react-types';
4-
import type { QueryClient, UseQueryOptions } from '@tanstack/react-query';
3+
import type {
4+
ServiceOperationInfiniteQueryKey,
5+
ServiceOperationQueryKey,
6+
} from '@openapi-qraft/tanstack-query-react-types';
7+
import type {
8+
QueryClient,
9+
QueryFunctionContext,
10+
UseQueryOptions,
11+
} from '@tanstack/react-query';
512
import type { CreateAPIQueryClientOptions } from '../qraftAPIClient.js';
613
import type { OperationSchema } from './requestFn.js';
14+
import { useMemo } from 'react';
715
import { composeInfiniteQueryKey } from './composeInfiniteQueryKey.js';
816
import { composeQueryKey } from './composeQueryKey.js';
917
import { prepareRequestFnParameters } from './prepareRequestFnParameters.js';
@@ -22,35 +30,54 @@ export function useComposeUseQueryOptions(
2230
): never {
2331
const [parameters, options] = args;
2432

25-
const queryFn =
26-
options?.queryFn ??
27-
function ({ queryKey: [, queryParams], signal, meta, pageParam }) {
28-
const { parameters, body } = prepareRequestFnParameters(
29-
queryParams,
30-
pageParam,
31-
infinite
32-
);
33-
34-
return qraftOptions
35-
.requestFn(schema, {
36-
parameters: parameters as never,
37-
baseUrl: qraftOptions.baseUrl,
38-
body,
39-
signal,
40-
meta,
41-
})
42-
.then(requestFnResponseResolver, requestFnResponseRejecter);
43-
};
44-
45-
const queryKey = Array.isArray(parameters)
46-
? (parameters as ServiceOperationQueryKey<OperationSchema, unknown>)
47-
: infinite
48-
? composeInfiniteQueryKey(schema, parameters)
49-
: composeQueryKey(schema, parameters);
33+
const queryFn = useMemo(
34+
() =>
35+
options?.queryFn ??
36+
qraftQueryFn.bind(null, qraftOptions.requestFn, qraftOptions.baseUrl),
37+
[qraftOptions.requestFn, qraftOptions.baseUrl, options?.queryFn]
38+
);
39+
40+
const queryKey = useMemo(
41+
() =>
42+
Array.isArray(parameters)
43+
? (parameters as ServiceOperationQueryKey<OperationSchema, unknown>)
44+
: infinite
45+
? composeInfiniteQueryKey(schema, parameters)
46+
: composeQueryKey(schema, parameters),
47+
[schema, parameters, infinite]
48+
);
5049

5150
return [{ ...options, queryFn, queryKey }, qraftOptions.queryClient] as never;
5251
}
5352

53+
function qraftQueryFn(
54+
requestFn: CreateAPIQueryClientOptions['requestFn'],
55+
baseUrl: CreateAPIQueryClientOptions['baseUrl'],
56+
{
57+
queryKey: [schema, queryParams],
58+
signal,
59+
meta,
60+
pageParam,
61+
}: QueryFunctionContext<
62+
| ServiceOperationQueryKey<OperationSchema, unknown>
63+
| ServiceOperationInfiniteQueryKey<OperationSchema, unknown>
64+
>
65+
) {
66+
const { parameters, body } = prepareRequestFnParameters(
67+
queryParams,
68+
pageParam,
69+
Boolean('infinite' in schema && schema.infinite)
70+
);
71+
72+
return requestFn(schema, {
73+
parameters: parameters as never,
74+
baseUrl,
75+
body,
76+
signal,
77+
meta,
78+
}).then(requestFnResponseResolver, requestFnResponseRejecter);
79+
}
80+
5481
type UseQueryOptionsArgs = [
5582
parameters: unknown,
5683
options?: UseQueryOptions,

0 commit comments

Comments
 (0)