Skip to content

Instantly share code, notes, and snippets.

@AshMW2724
Last active June 19, 2024 23:13
Show Gist options
  • Save AshMW2724/7c7d248c35db3a894376686025e2df67 to your computer and use it in GitHub Desktop.
Save AshMW2724/7c7d248c35db3a894376686025e2df67 to your computer and use it in GitHub Desktop.
openapi-typescript react-query

openai-typescript react-query

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!

openapi-ts/openapi-typescript#1406

// 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,
});
},
};
@hungify2022
Copy link

hungify2022 commented Feb 27, 2024

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?

@antifuchs
Copy link

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,
    });
  },
};

@loick
Copy link

loick commented Jun 19, 2024

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! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment