🌊
OrvalでReact-QueryのHooksを生成する
はじめに
こちらの記事でOrvalを使ってswrのHooksを作成しました。
swrでは、PostやPutなどのミューテーションのHooksは生成されませんでした。(ラップしたものは生成してくれます)
出来れば、PostメソッドなどのミューテーションのHooksも生成されて欲しいです。
ということで、React-Queryを使った場合のHooksも見てみることにしました。
インストールなど
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サーバーを起動してください。
組み込んでみる
生成したコードには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まで生成出来ることが知れたのは収穫でした😆
フォームを作るときにより便利になりますので、使って行きたいなと思いました!
Discussion