🌊

OrvalでReact-QueryのHooksを生成する

2023/11/01に公開

はじめに

こちらの記事でOrvalを使ってswrのHooksを作成しました。
swrでは、PostやPutなどのミューテーションのHooksは生成されませんでした。(ラップしたものは生成してくれます)
出来れば、PostメソッドなどのミューテーションのHooksも生成されて欲しいです。

ということで、React-Queryを使った場合のHooksも見てみることにしました。

インストールなど

OpenApiドキュメントのサンプルとツールインストールは以下の記事の通りに行います。
https://zenn.dev/collabostyle/articles/a47d3a31b27650#openapiのドキュメントを作成してみます

設定ファイルの作成

clientの値をreact-queryにします。

./orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  backend: {
    input: {
      target: "./openapi/openapi.oas3.yml",
    },
    output: {
      target: "./src/api/backend.ts",
      clean: true,
      client: "react-query",
    },
  },
});

Hooksの生成

以下のコマンドでHooksを生成します。

npx orval

swrと異なり、ミューテーションのHooksが生成されています😆

生成したHooks
./src/api/backend.ts
/**
 * Generated by orval v6.19.1 🍺
 * Do not edit manually.
 * サンプルAPI
 * サンプル用のAPIです
 * OpenAPI spec version: 0.0.1
 */
import {
  useMutation,
  useQuery
} from '@tanstack/react-query'
import type {
  MutationFunction,
  QueryFunction,
  QueryKey,
  UseMutationOptions,
  UseQueryOptions,
  UseQueryResult
} from '@tanstack/react-query'
import axios from 'axios'
import type {
  AxiosError,
  AxiosRequestConfig,
  AxiosResponse
} from 'axios'
export type Discriminator = string;

export interface User {
  discriminator?: Discriminator;
  email?: string;
  name?: string;
  note?: string;
}





/**
 * @summary ユーザー一覧取得
 */
export const getUsers = (
     options?: AxiosRequestConfig
 ): Promise<AxiosResponse<User[]>> => {
    
    return axios.get(
      `/users`,options
    );
  }


export const getGetUsersQueryKey = () => {
    
    return [`/users`] as const;
    }

    
export const getGetUsersQueryOptions = <TData = Awaited<ReturnType<typeof getUsers>>, TError = AxiosError<void>>( options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getUsers>>, TError, TData>, axios?: AxiosRequestConfig}
) => {

const {query: queryOptions, axios: axiosOptions} = options ?? {};

  const queryKey =  queryOptions?.queryKey ?? getGetUsersQueryKey();

  

    const queryFn: QueryFunction<Awaited<ReturnType<typeof getUsers>>> = ({ signal }) => getUsers({ signal, ...axiosOptions });

      

      

   return  { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getUsers>>, TError, TData> & { queryKey: QueryKey }
}

export type GetUsersQueryResult = NonNullable<Awaited<ReturnType<typeof getUsers>>>
export type GetUsersQueryError = AxiosError<void>

/**
 * @summary ユーザー一覧取得
 */
export const useGetUsers = <TData = Awaited<ReturnType<typeof getUsers>>, TError = AxiosError<void>>(
  options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getUsers>>, TError, TData>, axios?: AxiosRequestConfig}

  ):  UseQueryResult<TData, TError> & { queryKey: QueryKey } => {

  const queryOptions = getGetUsersQueryOptions(options)

  const query = useQuery(queryOptions) as  UseQueryResult<TData, TError> & { queryKey: QueryKey };

  query.queryKey = queryOptions.queryKey ;

  return query;
}


/**
 * @summary ユーザー作成
 */
export const postUsers = (
    user: User, options?: AxiosRequestConfig
 ): Promise<AxiosResponse<void>> => {
    
    return axios.post(
      `/users`,
      user,options
    );
  }



export const getPostUsersMutationOptions = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof postUsers>>, TError,{data: User}, TContext>, axios?: AxiosRequestConfig}
): UseMutationOptions<Awaited<ReturnType<typeof postUsers>>, TError,{data: User}, TContext> => {
 const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};

      


      const mutationFn: MutationFunction<Awaited<ReturnType<typeof postUsers>>, {data: User}> = (props) => {
          const {data} = props ?? {};

          return  postUsers(data,axiosOptions)
        }

        


   return  { mutationFn, ...mutationOptions }}

    export type PostUsersMutationResult = NonNullable<Awaited<ReturnType<typeof postUsers>>>
    export type PostUsersMutationBody = User
    export type PostUsersMutationError = AxiosError<unknown>

    /**
 * @summary ユーザー作成
 */
export const usePostUsers = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof postUsers>>, TError,{data: User}, TContext>, axios?: AxiosRequestConfig}
) => {

      const mutationOptions = getPostUsersMutationOptions(options);

      return useMutation(mutationOptions);
    }
    
/**
 * @summary ユーザー情報取得
 */
export const getUsersDiscriminator = (
    discriminator: Discriminator, options?: AxiosRequestConfig
 ): Promise<AxiosResponse<User>> => {
    
    return axios.get(
      `/users/${discriminator}`,options
    );
  }


export const getGetUsersDiscriminatorQueryKey = (discriminator: Discriminator,) => {
    
    return [`/users/${discriminator}`] as const;
    }

    
export const getGetUsersDiscriminatorQueryOptions = <TData = Awaited<ReturnType<typeof getUsersDiscriminator>>, TError = AxiosError<void>>(discriminator: Discriminator, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getUsersDiscriminator>>, TError, TData>, axios?: AxiosRequestConfig}
) => {

const {query: queryOptions, axios: axiosOptions} = options ?? {};

  const queryKey =  queryOptions?.queryKey ?? getGetUsersDiscriminatorQueryKey(discriminator);

  

    const queryFn: QueryFunction<Awaited<ReturnType<typeof getUsersDiscriminator>>> = ({ signal }) => getUsersDiscriminator(discriminator, { signal, ...axiosOptions });

      

      

   return  { queryKey, queryFn, enabled: !!(discriminator), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getUsersDiscriminator>>, TError, TData> & { queryKey: QueryKey }
}

export type GetUsersDiscriminatorQueryResult = NonNullable<Awaited<ReturnType<typeof getUsersDiscriminator>>>
export type GetUsersDiscriminatorQueryError = AxiosError<void>

/**
 * @summary ユーザー情報取得
 */
export const useGetUsersDiscriminator = <TData = Awaited<ReturnType<typeof getUsersDiscriminator>>, TError = AxiosError<void>>(
 discriminator: Discriminator, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getUsersDiscriminator>>, TError, TData>, axios?: AxiosRequestConfig}

  ):  UseQueryResult<TData, TError> & { queryKey: QueryKey } => {

  const queryOptions = getGetUsersDiscriminatorQueryOptions(discriminator,options)

  const query = useQuery(queryOptions) as  UseQueryResult<TData, TError> & { queryKey: QueryKey };

  query.queryKey = queryOptions.queryKey ;

  return query;
}


/**
 * @summary ユーザー情報変更
 */
export const putUsersDiscriminator = (
    discriminator: Discriminator,
    user: User, options?: AxiosRequestConfig
 ): Promise<AxiosResponse<void>> => {
    
    return axios.put(
      `/users/${discriminator}`,
      user,options
    );
  }



export const getPutUsersDiscriminatorMutationOptions = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof putUsersDiscriminator>>, TError,{discriminator: Discriminator;data: User}, TContext>, axios?: AxiosRequestConfig}
): UseMutationOptions<Awaited<ReturnType<typeof putUsersDiscriminator>>, TError,{discriminator: Discriminator;data: User}, TContext> => {
 const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};

      


      const mutationFn: MutationFunction<Awaited<ReturnType<typeof putUsersDiscriminator>>, {discriminator: Discriminator;data: User}> = (props) => {
          const {discriminator,data} = props ?? {};

          return  putUsersDiscriminator(discriminator,data,axiosOptions)
        }

        


   return  { mutationFn, ...mutationOptions }}

    export type PutUsersDiscriminatorMutationResult = NonNullable<Awaited<ReturnType<typeof putUsersDiscriminator>>>
    export type PutUsersDiscriminatorMutationBody = User
    export type PutUsersDiscriminatorMutationError = AxiosError<unknown>

    /**
 * @summary ユーザー情報変更
 */
export const usePutUsersDiscriminator = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof putUsersDiscriminator>>, TError,{discriminator: Discriminator;data: User}, TContext>, axios?: AxiosRequestConfig}
) => {

      const mutationOptions = getPutUsersDiscriminatorMutationOptions(options);

      return useMutation(mutationOptions);
    }
    
/**
 * @summary ユーザー削除
 */
export const deleteUsersDiscriminator = (
    discriminator: Discriminator, options?: AxiosRequestConfig
 ): Promise<AxiosResponse<void>> => {
    
    return axios.delete(
      `/users/${discriminator}`,options
    );
  }



export const getDeleteUsersDiscriminatorMutationOptions = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof deleteUsersDiscriminator>>, TError,{discriminator: Discriminator}, TContext>, axios?: AxiosRequestConfig}
): UseMutationOptions<Awaited<ReturnType<typeof deleteUsersDiscriminator>>, TError,{discriminator: Discriminator}, TContext> => {
 const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};

      


      const mutationFn: MutationFunction<Awaited<ReturnType<typeof deleteUsersDiscriminator>>, {discriminator: Discriminator}> = (props) => {
          const {discriminator} = props ?? {};

          return  deleteUsersDiscriminator(discriminator,axiosOptions)
        }

        


   return  { mutationFn, ...mutationOptions }}

    export type DeleteUsersDiscriminatorMutationResult = NonNullable<Awaited<ReturnType<typeof deleteUsersDiscriminator>>>
    
    export type DeleteUsersDiscriminatorMutationError = AxiosError<unknown>

    /**
 * @summary ユーザー削除
 */
export const useDeleteUsersDiscriminator = <TError = AxiosError<unknown>,
    
    TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof deleteUsersDiscriminator>>, TError,{discriminator: Discriminator}, TContext>, axios?: AxiosRequestConfig}
) => {

      const mutationOptions = getDeleteUsersDiscriminatorMutationOptions(options);

      return useMutation(mutationOptions);
    }
    

Mockサーバを立てる

こちらの記事を参考にMockサーバーを起動してください。
https://zenn.dev/collabostyle/articles/22660415d80f1d

組み込んでみる

生成したコードにはreact-queryとaxiosが含まれるのでインストールします。

npm i axios @tanstack/react-query

プロバイダを作成・適応する

Axiosの設定

./src/app/AxiosProvider.tsx
"use client"

import axios from "axios"

export default function AxiosProvider({
  children,
}: {
  children: React.ReactNode
}) {
  axios.defaults.baseURL="http://localhost:8080"

  return children
}

React-Queryの設定

Next.jsの場合はプロバイダを用意して設定を入れる必要があります。

以下のコマンドでインストールを行います。

npm i @tanstack/react-query-next-experimental
./src/app/ReactQueryProvider.tsx
"use client"

import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental"
import React from "react"

export default function ReactQueryProvider({
  children,
}: {
  children: React.ReactNode
}) {
  const [queryClient] = React.useState(() => new QueryClient())
  
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {children}
      </ReactQueryStreamedHydration>
    </QueryClientProvider>
  );
}

プロバイダを適応します

./src/app/AxiosProvider.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import AxiosProvider from './AxiosProvider'
import ReactQueryProvider from './ReactQueryProvider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <AxiosProvider>
      <ReactQueryProvider>
        <html>
          <body className={inter.className}>{children}</body>
        </html>
      </ReactQueryProvider>
    </AxiosProvider>
  )
}

GetメソッドのApiを組み込む

swrを使った時と同じソースコードになります。
ソースコードを変えなくていいんだ、、
細かい部分はケアしなければならないと思いますが、いいですね😊

./src/app/page.tsx
"use client"
import { useGetUsers } from '@/api/backend'

export default function Home() {

  const { data: users, error, isLoading } = useGetUsers();

  if(isLoading) {
    return <div>読み込み中</div>
  }

  if (error) {
    return <div>ユーザーの取得に失敗しました</div>
  }

  return (
    <>
      <div>ユーザー</div>
      <table>
        <tbody>
          {
            users?.data.map((user) => {
              return(
              <tr key={user.discriminator}>
                <td>{user.name}</td>
                <td>{user.discriminator}</td>
                <td>{user.email}</td>
                <td>{user.note}</td>
              </tr>
            )
          })
          }
        </tbody>
      </table>
    </>
  )
}

PostメソッドのApiを組み込む

こんな感じに組み込むことが出来ました!
とっても便利に使うことが出来ます😭
自前でuseStateしなくていいのはとっても素晴らしいです😆

./src/app/post-sample/page.tsx
"use client"
import { usePostUsers } from '@/api/backend'

export default function Home() {
  const {mutate, status} = usePostUsers();  
  console.log(status);

  return (
    <>
      <button disabled={status==="pending"} onClick={()=>{
        mutate({
          data: {
            name: "あお",
            discriminator: "ao39",
            email: "ao@example.com",
            note: "サンプルです"
          }
        })
      }}>ポストする</button>
      <div>{status}</div>
    </>
  )
}

おわりに

React-Queryを使えばミューテーションのHooksまで生成出来ることが知れたのは収穫でした😆
フォームを作るときにより便利になりますので、使って行きたいなと思いました!

コラボスタイル Developers

Discussion