宮崎からメリークリスマス🎅🌴 yoshinoです。
この記事は ESM Advent Calendar 2022 - Adventar 24日目の記事です。
私が参加しているプロジェクトではフロントエンドでNext.jsを利用していて、React.ComponentのUI管理にStorybookを利用しています。
Storybookはとても便利な反面、少しこった書き方をしようとすると、どうやって書けば良いのだっけ?となることがありました。 そこで、今回は、外部ライブラリのReact Contextを使うReact.Componentのstoryをどのように書くことができるかを考えてみようと思います。
対象となるコンポーネント
Auth0のSDKであるライブラリ(auth0-react)が提供するuseAuth0()
というAuth0Context.Provider
のcontextから値を取り出すメソッドを使っているコンポーネントを例として考えます。イメージはこんな感じです。
import { useAuth0 } from "@auth0/auth0-react" function Auth0Menu() { const { isAuthenticated } = useAuth0() // isAuthenticated は型がboolean return <div>{isAuthenticated ? <p>ログイン済み</p> : <p>未ログイン</p>}</div> } export default Auth0Menu
この記事では、StorybookのControlタブでisAuthenticated
の値を変更する方法を考えます。
うまくいったストーリー
結論としては、decoratorsのcontext経由でargsをわたすことで、想定した動作が実現できました 🌴
import React, { useState } from "react" import { ComponentMeta, Story } from "@storybook/react" import Auth0Menu from "../../components/Auth0Menu" import { Auth0Context, Auth0ContextInterface } from "@auth0/auth0-react" export default { title: "Auth0Menu", component: Auth0Menu, decorators: [ (Story, context) => { return ( {/* contextからはargsをはじめ様々な値を取り出すことができる */} <Auth0Context.Provider value={{isAuthenticated: context.args.isAuthenticated} as Auth0ContextInterface}> <Story /> </Auth0Context.Provider> ) } ] } as ComponentMeta<typeof Auth0Menu> const Template: Story<typeof Auth0Menu & {isAuthenticated: boolean}> = ({isAuthenticated, ...args}) => <Auth0Menu {...args} /> export const Default = Template.bind({}) // argsで定義した値はStoryBookのcotnrolsタブで変更することができる Default.args = { isAuthenticated: true }
以上になります 🙆
思った通り動かなかったストーリー
番外編です。最初はMocking importsを利用してやることも検討していました。しかし、この方法では、Controlタブを使って動的に値を変更することはできませんでした。 parametersを使うので、異なるStory(異なるファイルで定義されているStory)であれば、違う値に設定することはできます。Storybookの理解の助けになったので、こちらも説明します。
__mocks__/auht0-react.ts
で、対象のメソッド(useAuth0
)をモックします。
import { StoryContext } from "@storybook/react" import { PartialStoryFn } from '@storybook/csf' let isAuthenticated: boolean; export function useAuth0() { return { isAuthenticated: isAuthenticated } } // Storybookが提供するdecorator()メソッドを使ってuseAuth0の返り値にparametersの値を使う // context.argsとして、argsにもアクセスできるが、Controlタブを使って動的に値を変更できない export function decorator(story: PartialStoryFn, context: StoryContext) { if (context.parameters && context.parameters.auth0) { isAuthenticated = context.parameters.auth0.isAuthenticated } return story() }
.storybook/main.js
でimport時に、うえで作成したファイルを読み込むように変更します。
webpackFinal: async (config) => { config.resolve.alias['@auth0/auth0-react'] = require.resolve('../stories/__mocks__/auth0-react.ts'); return config; },
.storybook/preview.js
でdecoratorの設定を読み込むようにします。
import { decorator } from '../stories/__mocks__/auth0-react'; export const decorators = [decorator];
あとはお好きなStoryで、こんな感じでparametersを設定してあげることで値のだし分けをすることができます。
export const Default = Template.bind({}) Default.parameters = { auth0: { isAuthenticated: true // or false } }
Controlタブを使って動的に値を変更する必要がないケースでは、この方法でも良いかと思います 🙆
おわりに
紹介した例は具体的なライブラリを使ったものでしたが、今回の全体的な流れは、Contextの値をcontrolタブを使って、だし分けしたい場合には、汎用的に使える方法かと思います。
それでは、良いクリスマスをー 🏄