This is an extremely crude example for a function that uses your openapi-typescript
output to make a dynamic hook with react-query
.
If you find this useful and make changes, please comment them!
-
-
Save AshMW2724/7c7d248c35db3a894376686025e2df67 to your computer and use it in GitHub Desktop.
// generated paths | |
import { paths } from '@/data/@generated/api'; | |
import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query'; | |
import { FetchOptions, FetchResponse } from 'openapi-fetch'; | |
// Required! `pnpm i -D openapi-typescript-helpers` | |
import type { FilterKeys, PathsWithMethod, HasRequiredKeys, HttpMethod } from 'openapi-typescript-helpers'; | |
// Import your client generated by `openapi-fetch` | |
import api from './api'; | |
type QueryOptions = { queryOpts?: Partial<UseQueryOptions> }; | |
type CreateUseQuery<Paths extends {}> = { | |
useFetch<T extends HttpMethod, P extends PathsWithMethod<Paths, T>>( | |
method: T, | |
url: P, | |
...init: HasRequiredKeys<FetchOptions<FilterKeys<Paths[P], T>>> extends never | |
? [((FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions) | undefined)?] | |
: [FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions] | |
): UseQueryResult< | |
FetchResponse<T extends keyof Paths[P] ? Paths[P][T] : unknown>['data'], | |
FetchResponse<T extends keyof Paths[P] ? Paths[P][T] : unknown>['error'] | |
>; | |
}; | |
export const createUseQuery: CreateUseQuery<paths> = { | |
// @ts-expect-error It does return the correct type | |
useFetch(method, url, ...init) { | |
const options = init[0]; | |
return useQuery({ | |
// @ts-expect-error Params does exist sometimes | |
queryKey: [url, options?.body, options?.params], | |
queryFn: async ({ signal }) => { | |
// @ts-expect-error All good, we know this method exists | |
const { data, error } = await api.fetch[method.toUpperCase()](url, { | |
// ^^^^^^^^^ This is your client generated by `openapi-fetch` | |
...options, | |
signal, | |
}); | |
if (data) return data; | |
throw new Error(error); | |
}, | |
...options?.queryOpts, | |
}); | |
}, | |
}; |
I'm using this in a tiny project that I set up, and with the following deps, there are a couple typechecking / usage changes that needed adjustment:
"dependencies": {
"@tanstack/react-query": "^5.24.8",
"openapi-fetch": "^0.9.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
I had to add the adjusted fetch options to the type signature in the UseQueryResult legs, and replaced api.fetch
with just api
(it's the return value of createClient<paths>()
in my app, which doesn't have a .fetch
key. There was a spurious ts-expect-error
too, that I removed. Here's the result, which works really well so far:
import { paths } from "./v1";
import {
UseQueryOptions,
UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import { FetchOptions, FetchResponse } from "openapi-fetch";
import type {
FilterKeys,
PathsWithMethod,
HasRequiredKeys,
HttpMethod,
} from "openapi-typescript-helpers";
// Import your client generated by `openapi-fetch`
import api from "./openapi-client";
type QueryOptions = { queryOpts?: Partial<UseQueryOptions> };
type CreateUseQuery<Paths extends {}> = {
useFetch<T extends HttpMethod, P extends PathsWithMethod<Paths, T>>(
method: T,
url: P,
...init: HasRequiredKeys<
FetchOptions<FilterKeys<Paths[P], T>>
> extends never
? [((FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions) | undefined)?]
: [FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions]
): UseQueryResult<
FetchResponse<
T extends keyof Paths[P] ? Paths[P][T] : unknown,
FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions
>["data"],
FetchResponse<
T extends keyof Paths[P] ? Paths[P][T] : unknown,
FetchOptions<FilterKeys<Paths[P], T>> & QueryOptions
>["error"]
>;
};
export const createUseQuery: CreateUseQuery<paths> = {
// @ts-expect-error It does return the correct type
useFetch(method, url, ...init) {
const options = init[0];
return useQuery({
queryKey: [url, options?.body, options?.params],
queryFn: async ({ signal }) => {
// @ts-expect-error All good, we know this method exists
const { data, error } = await api[method.toUpperCase()](url, {
...options,
signal,
});
if (data) return data;
throw new Error(error);
},
...options?.queryOpts,
});
},
};
Hi @antifuchs and thanks for sharing!
It doesn't seem to be working on my side, how do you call it? Like this? Not sure I get why there is an object wrapper here instead of exposing a function directly, I'm probably missing something I'm sorry.
createUseQuery. useFetch('get', 'url', {})
I have a few Typescript errors, likely due to the updated versions:
Generic type 'FetchResponse' requires 3 type argument(s).
Thanks! 🙏
Looks good, I haven't tested it yet.
But how about useMutaion, any hook for this?
Could you provide a code example of how to use it?