useSWRを使ったReactコンポーネントをStorybookで正しく表示する方法
概要
こんにちは、駆け出しWebエンジニアのふっけーです。今回はuseSWRを使ったReactコンポーネントをStorybookに正しく表示する方法を紹介します。
useSWRを使ったコンポーネントを正しく表示させる際、useSWRで呼び出されているAPIをMockする必要があります。Mockのやり方としては2つあります:
- MockServiceWorkerを使ったやり方
- useSWRのMiddlewareを使ったやり方
MockServiceWorkerに関する記事は多くあったので、今回はMiddlewareを使ったやり方について紹介したいと思います。
対象読者
- NextJS+useSWR+Hono+Storybookの構成でUIを確認したい人
- useSWRの初心者〜中級者
- Storybookが何となくわかる (基本的な設定・decoratorsがわかる)
サンプルコンポーネント
このコンポーネントは渡されたid
をもとにAPIからデータを取得し、そのデータを表示するシンプルな構成です。
UIコンポーネント (ShowAPIData.tsx)
import { useAPIData } from "@/hooks/useAPIData"
type Props = {
id: string
}
export default function ShowAPIData({id}: Props) {
const { data, isLoading, isError } = useAPIData(id)
return (
<>
{!isLoading && !isError && data && (
<p>{data}</p>
)}
</>
)
}
Hono Endpoints
import { Hono } from "hono"
export const app = new Hono().basePath("/api")
export const route = app
.get("/",zValidator("query",z.object({ id: z.string() })),async (c) => {
return c.json({
data: "APIResponse", //今回はこちらはこのようにして割愛させていただきます
})
})
useAPIData Hooks
import useSWR from "swr"
import { hc, InferRequestType } from "hono/client"
import type { AppType } from "@/be"
export const useAPIData = (id: string) => {
const client = hc<AppType>("/")
const $get = client.api.$get
const fetcher = async (args: InferRequestType<typeof $get>) => {
const res = await $get(args)
if (!res.ok) {
return undefined
}
const { data } = await res.json()
return data
}
const { data, error, isLoading } = useSWR(
id
? {
query: {
id: id,
},
}
: null,
fetcher,
)
return {
data,
isLoading,
isError: error,
}
}
UIコンポーネントに対応するStory
import ShowAPIData from "./ShowAPIData"
const meta = {
title: "ShowAPIData",
component: ShowAPIData,
argTypes: {
id: {
control: "text",
description: "",
},
},
decorators: [
(Story) => (
<SWRConfig value={{ use: [testMiddleWare] }}>
<Story />
</SWRConfig>
),
],
} satisfies Meta<typeof ShowAPIData>
export default meta
export const Default: Story = {
args: {
id: "1234"
}
}
Middlewareを適用していきましょう
このままだと、argsをどんなに変えてもなにも表示されません。そこで、useSWRのMiddlewareを導入していきましょう
Middlewareはテストだけでなく、loggerを実行したい場合などにも使えます。公式Docsにはこのような記載がありました:
ミドルウェアは SWR フックを受け取り、実行の前後にロジックを実行できます。複数のミドルウェアがある場合、各ミドルウェアは次のミドルウェアをラップします。リストの最後のミドルウェアは、元の SWR フックである useSWR を受け取ります。
function myMiddleware (useSWRNext) { return (key, fetcher, config) => { // フックが実行される前... // 次のミドルウェア、またはこれが最後のミドルウェアの場合は `useSWR` を処理します。 const swr = useSWRNext(key, fetcher, config) // フックが実行された後... return swr } }
オプションとして、ミドルウェアの配列を SWRConfig または useSWR に渡すことができます:
<SWRConfig value={{ use: [myMiddleware] }}> // または... useSWR(key, fetcher, { use: [myMiddleware] })
JSXを使ってSWRConfigをStorybookに注入できるため、Mockの適用が柔軟かつ簡潔に行えるのが特徴です。
これらを踏まえてMiddlewareを追加していきましょう
import ShowAPIData from "./ShowAPIData"
type MockData = {
key: string
data: string
}
const mockData: MockData[] = [
{
key: "empty_data",
data: "",
},
{
key: "very_short_data",
data: "a",
},
{
key: "normal_data",
data: "abcdefg",
},
{
key: "very_long_data",
data: "abcdefghijklmnopqrstuvwxyz",
}
]
const testMiddleWare: Middleware = () => {
return (key): SWRResponse => {
// SWRキーからidを抽出
let id: string = "normal_data" // デフォルト値
if (key && typeof key === "object" && "query" in key) {
const query = key.query as { id?: string }
id = query.id || ""
}
const mockEntry = mockData.find((mock) => mock.key === id)?.data
return {
data: mockEntry,
error: undefined,
mutate: () => Promise.resolve(),
isValidating: false,
isLoading: false,
}
}
}
const meta = {
title: "ShowAPIData",
component: ShowAPIData,
argTypes: {
id: {
control: "text",
description: "",
},
}
} satisfies Meta<typeof ShowAPIData>
export default meta
export const Default: Story = {
args: {
id: "1234"
}
}
ここのMiddlewareで行なっていることは、SWRのレスポンスであるSWRResponseをMockしてreturnしているところです。
Docsにも書いてある通り、リストの最後のミドルウェアは、元の SWR フックである useSWR を受け取ります。
のため、実際にAPIを叩くことなくMockのResponseを返すことができるのです。
また、少しわかりにくいですがHonoのClientを使っているためidを取得するときに下記のような書き方をしています:
let id: string = "normal_data" // デフォルト値
if (key && typeof key === "object" && "query" in key) {
const query = key.query as { id?: string }
id = query.id || ""
}
これは、keyがanyになってしまうためtypeofで型チェックを挟むことでSWRのqueryから取得できるようにしています
まとめ
- useSWRのMiddlewareを用いるとMock Service Workerを使うことなくAPIをMockすることができる
- Middlewareを使ったときにswrのqueryから引数を持ってくるときには少し工夫が必要だった
最後まで読んでいただきありがとうございました!
記事が参考になりましたら❤️お願いします!
Discussion