-
-
Notifications
You must be signed in to change notification settings - Fork 501
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
openapi-react-query: Add easy way of invalidating queries #1806
Comments
@kerwanp I'll work on that if you want, this feature will help me a lot. |
@iffa after taking closer look, I don't think you'll need ability to inject QueryClient. Methods from openapi-react-query are using import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
const queryClient = new QueryClient();
const QueryProvider = ({ children }) => {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
export {QueryProvider, queryClient} Just wrap your app with |
Ah, nice. Just need an easier way to get the generated query keys and this is a good approach |
@iffa Could you provide an example of how interface for that should look like? |
Not sure if it is an ideal approach but one way would be to make the return {queryKey: [...], ...useQuery({
queryKey: [method, path, init],
queryFn: async () => {
const mth = method.toUpperCase() as keyof typeof client;
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
const { data, error } = await fn(path, init as any); // TODO: find a way to avoid as any
if (error || !data) {
throw error;
}
return data;
},
...options,
})}; But one may also want to invalidate based on the first 2 parts of the query key (method, path) so I dunno how that would work |
@iffa I think it should be done in |
Maybe something along the lines of this: export type GetQueryKeyMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends FetchOptions<FilterKeys<Paths[Path], Method>> | undefined,
>(
method: Method,
url: Path,
init?: Init,
) => ReadonlyArray<unknown>;
export interface OpenapiQueryClient<
Paths extends {},
Media extends MediaType = MediaType,
> {
useQuery: UseQueryMethod<Paths, Media>;
getQueryKey: GetQueryKeyMethod<Paths>;
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
useMutation: UseMutationMethod<Paths, Media>;
}
export default function createClient<
Paths extends {},
Media extends MediaType = MediaType,
>(client: FetchClient<Paths, Media>): OpenapiQueryClient<Paths, Media> {
return {
getQueryKey: (method, path, init) => {
return [method, path, ...(init ? [init] : [])] as const;
},
// ... rest |
Hey! We clearly have to find a way to give the ability to easily invalidate queries. I think the idea of an helper function works great, your example should work @iffa. I think the function could even be defined outside the createClient so it can be used when generating the real query keys. An other idea I had in mind was to abstract even more around tanstack: const usePostsQuery = $api.createQuery('/api/posts/{id}');
const useCreatePostMutation = $api.createMutation('/api/posts/{id}');
export const Example1 = () => {
const { query } = usePostsQuery();
const { mutate } = useCreatePostMutation(data, {
onSuccess: {
queryClient.invalidateQueries({ queryKey: usePostsQuery.queryKey })
}
});
}
export const Example2 = () => {
const { query } = usePostsQuery();
const { mutate } = useCreatePostMutation(data, {
onSuccess: () => {
usePostsQuery.invalidate()
}
});
} We could then imagine some kind of factory to work with CRUDS: const $posts = $api.createCrud({
create: '/api/posts',
get: '/api/posts/{id}',
delete: '/api/posts/{id}'
});
export const Example = () => {
const { query } = $posts.useGet(5);
const { mutate } = $posts.useCreate();
} With optimistic updates, automatic invalidation, etc. |
In my opinion |
I agree with that, so let's go with @iffa example. $api.keys('get', '/api/posts');
$api.getKeys('get', '/api/posts');
$api.generateKeys('get', '/api/posts'); |
React Query has a const options = queryOptions('get', '/endpoint', {})
queryClient.invalidateQueries(options)
// mutate cache
queryClient.setQueryData(options, (prevData) => ({
...prevData
})
// and this one is important to be able to fully use useSuspenseQuery
queryClient.prefetchQuery(options) |
Added this to my project, working nicely. import {
queryOptions as tanstackQueryOptions,
QueryOptions,
DefinedInitialDataOptions,
} from '@tanstack/react-query'
import type {
ClientMethod,
FetchResponse,
MaybeOptionalInit,
Client as FetchClient,
} from 'openapi-fetch'
import type {
HttpMethod,
MediaType,
PathsWithMethod,
} from 'openapi-typescript-helpers'
import { getClient } from './api'
type QueryOptionsMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Media extends MediaType = MediaType,
> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
Options extends Omit<
QueryOptions<Response['data'], Response['error']>,
'queryKey' | 'queryFn'
>,
>(
method: Method,
path: Path,
init?: Init & { [key: string]: unknown },
options?: Options,
) => DefinedInitialDataOptions<Response['data'], Response['error']>
interface QueryOptionsReturn<
Paths extends {},
Media extends MediaType = MediaType,
> {
queryOptions: QueryOptionsMethod<Paths, Media>
}
const createQueryOptions = <
Paths extends {},
Media extends MediaType = MediaType,
>(
client: FetchClient<Paths, Media>,
): QueryOptionsReturn<Paths, Media> => {
return {
queryOptions: (method, path, init, options) => {
return tanstackQueryOptions({
queryKey: [method, path, init],
queryFn: async () => {
const mth = method.toUpperCase() as keyof typeof client
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>
const { data, error } = await fn(path, init as any) // TODO: find a way to avoid as any
if (error || !data) {
throw error
}
return data
},
...(options as any),
})
},
}
}
export const { queryOptions } = createQueryOptions(getClient())
export type InferQueryData<T> =
T extends DefinedInitialDataOptions<infer D, any> ? Partial<D> : never |
I like a lot this solution as it follows the api of @tanstack/react-query. $api.queryOptions('get', '/api/posts'); This should also make the implementation of the following issues much easier and cleaner:
@ZsugaBubu the proposed solution is really similar to your PR, do you want to take this issue? |
What about creating top-level (outside // { queryKey: ['get', '/items/{id}'], stale: true }
queryFilters<Paths>('get', '/items/{id}', undefined, { stale: true }));
// { queryKey: ['get', '/items/{id}', { params: ... }] }
queryFilters<Paths>('get', '/items/{id}', { params: { path: { id: 'foo' } } });
// { queryKey: ['get', '/items/{id}'], predicate: () => ... }
queryFilters<Paths>('get', '/items/{id}', init => init.params.path.id == 'foo');
// And use it like:
queryClient.invalidateQueries(queryFilters<Paths>(...)); |
@zsugabubus the init and options param is optional, so you can also support non exact matching queries. |
For urls that do not include an init param, generate a query key of length 2. This allows the value to be passed directly to `invalidateQueries()`. Without this change, the package generates `["get", "/foo", undefined]` which does not correctly match the corresponding get query. Related to openapi-ts#1806
For urls that do not include an init param, generate a query key of length 2. This allows the value to be passed directly to `invalidateQueries()`. Without this change, the package generates `["get", "/foo", undefined]` which does not correctly match the corresponding get query. Related to openapi-ts#1806
* Drop init argument when not needed in query key For urls that do not include an init param, generate a query key of length 2. This allows the value to be passed directly to `invalidateQueries()`. Without this change, the package generates `["get", "/foo", undefined]` which does not correctly match the corresponding get query. Related to #1806 * Create sour-steaks-double.md
I want to update the cache using |
That's one of the use cases yes :) |
There is currently no easy way to invalidate queries. #1804 and #1805 together would solve this by allowing us to do it ourselves, but a built-in mechanism could also be helpful.
The text was updated successfully, but these errors were encountered: