Skip to content

feat: automatic optimistic update for tanstack hooks #830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 72 additions & 9 deletions packages/plugins/tanstack-query/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,23 @@ function generateQueryHook(
overrideReturnType?: string,
overrideInputType?: string,
overrideTypeParameters?: string[],
infinite = false
infinite = false,
optimisticUpdate = false
) {
const capOperation = upperCaseFirst(operation);

const argsType = overrideInputType ?? `Prisma.${model}${capOperation}Args`;
const inputType = `Prisma.SelectSubset<T, ${argsType}>`;
const returnType =
overrideReturnType ?? (returnArray ? `Array<Prisma.${model}GetPayload<T>>` : `Prisma.${model}GetPayload<T>`);

let defaultReturnType = `Prisma.${model}GetPayload<T>`;
if (optimisticUpdate) {
defaultReturnType += '& { $optimistic?: boolean }';
}
if (returnArray) {
defaultReturnType = `Array<${defaultReturnType}>`;
}

const returnType = overrideReturnType ?? defaultReturnType;
const optionsType = makeQueryOptions(target, returnType, infinite, version);

const func = sf.addFunction({
Expand All @@ -100,6 +109,15 @@ function generateQueryHook(
name: 'options?',
type: optionsType,
},
...(optimisticUpdate
? [
{
name: 'optimisticUpdate',
type: 'boolean',
initializer: 'true',
},
]
: []),
],
isExported: true,
});
Expand All @@ -113,7 +131,7 @@ function generateQueryHook(
makeGetContext(target),
`return ${infinite ? 'useInfiniteModelQuery' : 'useModelQuery'}('${model}', \`\${endpoint}/${lowerCaseFirst(
model
)}/${operation}\`, args, options, fetch);`,
)}/${operation}\`, args, options, fetch${optimisticUpdate ? ', optimisticUpdate' : ''});`,
]);
}

Expand Down Expand Up @@ -154,6 +172,11 @@ function generateMutationHook(
type: 'boolean',
initializer: 'true',
},
{
name: 'optimisticUpdate',
type: 'boolean',
initializer: 'false',
},
],
});

Expand All @@ -170,7 +193,7 @@ function generateMutationHook(
overrideReturnType ?? model
}, ${checkReadBack}>('${model}', '${httpVerb.toUpperCase()}', \`\${endpoint}/${lowerCaseFirst(
model
)}/${operation}\`, metadata, options, fetch, invalidateQueries, ${checkReadBack})
)}/${operation}\`, metadata, options, fetch, invalidateQueries, ${checkReadBack}, optimisticUpdate)
`,
},
],
Expand Down Expand Up @@ -272,8 +295,6 @@ function generateModelHooks(
// findMany
if (mapping.findMany) {
// regular findMany
generateQueryHook(target, version, sf, model.name, 'findMany', true, true);
// infinite findMany
generateQueryHook(
target,
version,
Expand All @@ -285,18 +306,60 @@ function generateModelHooks(
undefined,
undefined,
undefined,
false,
true
);
// infinite findMany
generateQueryHook(
target,
version,
sf,
model.name,
'findMany',
true,
true,
undefined,
undefined,
undefined,
true,
false
);
}

// findUnique
if (mapping.findUnique) {
generateQueryHook(target, version, sf, model.name, 'findUnique', false, false);
generateQueryHook(
target,
version,
sf,
model.name,
'findUnique',
false,
false,
undefined,
undefined,
undefined,
false,
true
);
}

// findFirst
if (mapping.findFirst) {
generateQueryHook(target, version, sf, model.name, 'findFirst', false, true);
generateQueryHook(
target,
version,
sf,
model.name,
'findFirst',
false,
true,
undefined,
undefined,
undefined,
false,
true
);
}

// update
Expand Down
47 changes: 40 additions & 7 deletions packages/plugins/tanstack-query/src/runtime-v5/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
makeUrl,
marshal,
setupInvalidation,
setupOptimisticUpdate,
type APIContext,
} from '../runtime/common';

Expand Down Expand Up @@ -50,18 +51,21 @@ export const Provider = RequestHandlerContext.Provider;
* @param url The request URL.
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query options object
* @param fetch The fetch function to use for sending the HTTP request
* @param optimisticUpdate Whether to enable automatic optimistic update
* @returns useQuery hook
*/
export function useModelQuery<R>(
model: string,
url: string,
args?: unknown,
options?: Omit<UseQueryOptions<R>, 'queryKey'>,
fetch?: FetchFn
fetch?: FetchFn,
optimisticUpdate = false
) {
const reqUrl = makeUrl(url, args);
return useQuery({
queryKey: getQueryKey(model, url, args),
queryKey: getQueryKey(model, url, args, false, optimisticUpdate),
queryFn: () => fetcher<R, false>(reqUrl, undefined, fetch, false),
...options,
});
Expand All @@ -74,6 +78,7 @@ export function useModelQuery<R>(
* @param url The request URL.
* @param args The initial request args object, URL-encoded and appended as "?q=" parameter
* @param options The react-query infinite query options object
* @param fetch The fetch function to use for sending the HTTP request
* @returns useInfiniteQuery hook
*/
export function useInfiniteModelQuery<R>(
Expand All @@ -84,14 +89,27 @@ export function useInfiniteModelQuery<R>(
fetch?: FetchFn
) {
return useInfiniteQuery({
queryKey: getQueryKey(model, url, args),
queryKey: getQueryKey(model, url, args, true),
queryFn: ({ pageParam }) => {
return fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);
},
...options,
});
}

/**
* Creates a react-query mutation
*
* @param model The name of the model under mutation.
* @param method The HTTP method.
* @param url The request URL.
* @param modelMeta The model metadata.
* @param options The react-query options.
* @param fetch The fetch function to use for sending the HTTP request
* @param invalidateQueries Whether to invalidate queries after mutation.
* @param checkReadBack Whether to check for read back errors and return undefined if found.
* @param optimisticUpdate Whether to enable automatic optimistic update
*/
export function useModelMutation<T, R = any, C extends boolean = boolean, Result = C extends true ? R | undefined : R>(
model: string,
method: 'POST' | 'PUT' | 'DELETE',
Expand All @@ -100,7 +118,8 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
options?: Omit<UseMutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
checkReadBack?: C,
optimisticUpdate = false
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) => {
Expand All @@ -118,10 +137,11 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
};

const finalOptions = { ...options, mutationFn };
if (invalidateQueries) {
const operation = url.split('/').pop();

if (operation) {
const { logging } = useContext(RequestHandlerContext);
const operation = url.split('/').pop();
if (operation) {
if (invalidateQueries) {
setupInvalidation(
model,
operation,
Expand All @@ -131,6 +151,19 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
logging
);
}

if (optimisticUpdate) {
setupOptimisticUpdate(
model,
operation,
modelMeta,
finalOptions,
queryClient.getQueryCache().getAll(),
(queryKey, data) => queryClient.setQueryData<unknown>(queryKey, data),
invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined,
logging
);
}
}

return useMutation(finalOptions);
Expand Down
33 changes: 26 additions & 7 deletions packages/plugins/tanstack-query/src/runtime-v5/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
makeUrl,
marshal,
setupInvalidation,
setupOptimisticUpdate,
} from '../runtime/common';

export { APIContext as RequestHandlerContext } from '../runtime/common';
Expand Down Expand Up @@ -53,17 +54,20 @@ export function getHooksContext() {
* @param url The request URL.
* @param args The request args object, URL-encoded and appended as "?q=" parameter
* @param options The svelte-query options object
* @param fetch The fetch function to use for sending the HTTP request
* @param optimisticUpdate Whether to enable automatic optimistic update
* @returns useQuery hook
*/
export function useModelQuery<R>(
model: string,
url: string,
args?: unknown,
options?: StoreOrVal<Omit<QueryOptions<R>, 'queryKey'>>,
fetch?: FetchFn
fetch?: FetchFn,
optimisticUpdate = false
) {
const reqUrl = makeUrl(url, args);
const queryKey = getQueryKey(model, url, args);
const queryKey = getQueryKey(model, url, args, false, optimisticUpdate);
const queryFn = () => fetcher<R, false>(reqUrl, undefined, fetch, false);

let mergedOpt: any;
Expand Down Expand Up @@ -103,7 +107,7 @@ export function useInfiniteModelQuery<R>(
options: StoreOrVal<Omit<CreateInfiniteQueryOptions<R, unknown, InfiniteData<R>>, 'queryKey'>>,
fetch?: FetchFn
) {
const queryKey = getQueryKey(model, url, args);
const queryKey = getQueryKey(model, url, args, true);
const queryFn = ({ pageParam }: { pageParam: unknown }) =>
fetcher<R, false>(makeUrl(url, pageParam ?? args), undefined, fetch, false);

Expand Down Expand Up @@ -151,7 +155,8 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
options?: Omit<MutationOptions<Result, unknown, T>, 'mutationFn'>,
fetch?: FetchFn,
invalidateQueries = true,
checkReadBack?: C
checkReadBack?: C,
optimisticUpdate = false
) {
const queryClient = useQueryClient();
const mutationFn = (data: any) => {
Expand All @@ -169,10 +174,11 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
};

const finalOptions = { ...options, mutationFn };
if (invalidateQueries) {
const operation = url.split('/').pop();

if (operation) {
const { logging } = getContext<APIContext>(SvelteQueryContextKey);
const operation = url.split('/').pop();
if (operation) {
if (invalidateQueries) {
setupInvalidation(
model,
operation,
Expand All @@ -182,6 +188,19 @@ export function useModelMutation<T, R = any, C extends boolean = boolean, Result
logging
);
}

if (optimisticUpdate) {
setupOptimisticUpdate(
model,
operation,
modelMeta,
finalOptions,
queryClient.getQueryCache().getAll(),
(queryKey, data) => queryClient.setQueryData<unknown>(queryKey, data),
invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined,
logging
);
}
}

return createMutation(finalOptions);
Expand Down
Loading