-
Notifications
You must be signed in to change notification settings - Fork 12
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
What is the best practice for invalidating queries in react-query-kit #62
Comments
Yeah, you can customize any behavior of this hook via middleware |
Here's the middleware I created
Then it can be used:
If you need to invalidate the query conditionally, based on the respond, then use the
If your queries are created with RQK, it provides |
@denisborovikov Hi, thanks for the Idea! I extended it to support Typescript and having possibility to specify method in which we have to invalidate, as well as possibility to provide import { Middleware, MutationHook } from "react-query-kit";
import {
InvalidateOptions,
InvalidateQueryFilters,
QueryKey,
useQueryClient,
} from "@tanstack/react-query";
import { HandledCustomError } from "~shared/api/lib/handleApiError";
type GuardFunctionOnSuccess<TData, TVariables, TContext> = (
data: TData,
variables: TVariables,
context: TContext
) => boolean;
type GuardFunctionOnSettled<
TData,
TError extends HandledCustomError,
TVariables,
TContext
> = (
data: TData | undefined,
error: TError | null,
variables: TVariables,
context: TContext | undefined
) => boolean;
interface FiltersInvalidateEntry {
type: "filters";
queryFilters: InvalidateQueryFilters;
}
interface GuardInvalidateEntryOnSuccess<TData, TVariables, TContext> {
type: "guard";
queryFilters: InvalidateQueryFilters;
guard: GuardFunctionOnSuccess<TData, TVariables, TContext>;
}
interface GuardInvalidateEntryOnSettled<
TData,
TError extends HandledCustomError,
TVariables,
TContext
> {
type: "guard";
queryFilters: InvalidateQueryFilters;
guard: GuardFunctionOnSettled<TData, TError, TVariables, TContext>;
}
type InvalidateEntryOnSuccess<TData, TVariables, TContext> =
| FiltersInvalidateEntry
| GuardInvalidateEntryOnSuccess<TData, TVariables, TContext>;
type InvalidateEntryOnSettled<
TData,
TError extends HandledCustomError,
TVariables,
TContext
> =
| FiltersInvalidateEntry
| GuardInvalidateEntryOnSettled<TData, TError, TVariables, TContext>;
type InvalidateEntriesOnSuccess<TData, TVariables, TContext> = (
| QueryKey
| InvalidateEntryOnSuccess<TData, TVariables, TContext>
)[];
type InvalidateEntriesOnSettled<
TData,
TError extends HandledCustomError,
TVariables,
TContext
> = (
| QueryKey
| InvalidateEntryOnSettled<TData, TError, TVariables, TContext>
)[];
function invalidateQueriesAfterMutationMiddleware<
TData,
TVariables,
TError extends HandledCustomError,
TContext
>(
entries: InvalidateEntriesOnSuccess<TData, TVariables, TContext>,
options: {
method?: "onSuccess";
invalidateOptions?: InvalidateOptions;
waitForInvalidation?: boolean;
}
): Middleware<MutationHook<TData, TVariables, TError, TContext>>;
function invalidateQueriesAfterMutationMiddleware<
TData,
TVariables,
TError extends HandledCustomError,
TContext
>(
entries: InvalidateEntriesOnSettled<TData, TError, TVariables, TContext>,
options?: {
method?: "onSettled";
invalidateOptions?: InvalidateOptions;
waitForInvalidation?: boolean;
}
): Middleware<MutationHook<TData, TVariables, TError, TContext>>;
function invalidateQueriesAfterMutationMiddleware<
TData,
TVariables,
TError extends HandledCustomError,
TContext
>(
entries:
| InvalidateEntriesOnSuccess<TData, TVariables, TContext>
| InvalidateEntriesOnSettled<TData, TError, TVariables, TContext>,
options?: {
method?: "onSuccess" | "onSettled";
invalidateOptions?: InvalidateOptions;
waitForInvalidation?: boolean;
}
): Middleware<MutationHook<TData, TVariables, TError, TContext>> {
return (useMutationNext) => {
return (mutationOptions) => {
const queryClient = useQueryClient();
const method = options?.method ?? "onSettled";
const shouldWaitForInvalidation = options?.waitForInvalidation ?? false;
if (method === "onSuccess") {
const onSuccess = async (
data: TData,
variables: TVariables,
context: TContext
) => {
const invalidationPromises: Promise<void>[] = [];
for (const entry of entries as InvalidateEntriesOnSuccess<
TData,
TVariables,
TContext
>) {
if (!Array.isArray(entry)) {
const keyEntry = entry as InvalidateEntryOnSuccess<
TData,
TVariables,
TContext
>;
// Entry is InvalidateEntryOnSuccess
if (keyEntry.type === "filters") {
const promise = queryClient.invalidateQueries(
keyEntry.queryFilters,
options?.invalidateOptions
);
invalidationPromises.push(promise);
} else if (keyEntry.type === "guard") {
if (keyEntry.guard(data, variables, context)) {
const promise = queryClient.invalidateQueries(
keyEntry.queryFilters,
options?.invalidateOptions
);
invalidationPromises.push(promise);
}
}
} else {
// Entry is QueryKey
const promise = queryClient.invalidateQueries(
{ queryKey: entry },
options?.invalidateOptions
);
invalidationPromises.push(promise);
}
}
if (shouldWaitForInvalidation) {
await Promise.all(invalidationPromises);
}
const originalOnSuccess = mutationOptions.onSuccess;
const result = originalOnSuccess?.(data, variables, context);
if (shouldWaitForInvalidation && result instanceof Promise) {
await result;
}
};
return useMutationNext({ ...mutationOptions, onSuccess });
} else if (method === "onSettled") {
const onSettled = async (
data: TData | undefined,
error: TError | null,
variables: TVariables,
context: TContext | undefined
) => {
const invalidationPromises: Promise<void>[] = [];
for (const entry of entries as InvalidateEntriesOnSettled<
TData,
TError,
TVariables,
TContext
>) {
if (typeof entry !== "string" && !Array.isArray(entry)) {
const keyEntry = entry as InvalidateEntryOnSettled<
TData,
TError,
TVariables,
TContext
>;
// Entry is InvalidateEntryOnSettled
if (keyEntry.type === "filters") {
const promise = queryClient.invalidateQueries(
keyEntry.queryFilters,
options?.invalidateOptions
);
invalidationPromises.push(promise);
} else if (keyEntry.type === "guard") {
if (keyEntry.guard(data, error, variables, context)) {
const promise = queryClient.invalidateQueries(
keyEntry.queryFilters,
options?.invalidateOptions
);
invalidationPromises.push(promise);
}
}
} else {
// Entry is QueryKey
const promise = queryClient.invalidateQueries(
{ queryKey: entry },
options?.invalidateOptions
);
invalidationPromises.push(promise);
}
}
if (shouldWaitForInvalidation) {
await Promise.all(invalidationPromises);
}
const originalOnSettled = mutationOptions.onSettled;
const result = originalOnSettled?.(data, error, variables, context);
if (shouldWaitForInvalidation && result instanceof Promise) {
await result;
}
};
return useMutationNext({ ...mutationOptions, onSettled });
}
return useMutationNext(mutationOptions);
};
};
}
export default invalidateQueriesAfterMutationMiddleware; |
Here's the example of usage: export const useAnotherMutation = createMutation({
mutationFn: async (variables) => {
// Your mutation logic
},
use: [
invalidateQueriesAfterMutationMiddleware(
[
someQuery.getKey(),
['some query key']
{
type: 'guard',
guard: (data, error, variables, context) => {
// Correct parameters for onSettled
return !error && data?.needsRefresh;
},
queryFilters: { queryKey: ['inventory'] },
},
],
{
method: 'onSettled', // by default
waitForInvalidation: false, // by default
}
),
],
}); |
I generally use the approach in the code below.
This approach does not use
useQueryClient()
hook and I just wanna know if this approach has any side effect. If there is a side effect, what can I do to prevent that side effect? Is middleware like approach a better solution for this? If it is, could we add this to documentation of react-query-kitThank in advance.
The text was updated successfully, but these errors were encountered: