🐡

Storybook tips: Contextを使っているコンポーネントのStoryを書く

2022/04/13に公開

StorybookのTipsです。

内部でuseContextを使っているコンポーネントをどうやってStoryに起こすかを解説します。

コンポーネントの例

以下のように、内部でAuthContextを使っているコンポーネントがあったとします。

export const Header = memo(() => {
  const { user } = useContext(AuthContext)

  return (
    <header>
      <ServiceLogo></ServiceLogo>
      {
        user && user.type === 'teacher' && <Text>{user.name}</Text>
      }
      {
        user && <LogOutButton></LogOutButton>
      }
    </header>
  )
})

これをそのままComponent Story Formatに落とし込むと、ContextでWrapされていないため動作しません。

export default {
  component: Header
} as ComponentMeta<typeof Header>

// 以下のそれぞれのストーリーで、Providerから返ってくるuserのオブジェクトが異なる
export const ByTeacher: ComponentStoryObj<typeof Header> = {
}
export const ByLogoutUser: ComponentStoryObj<typeof Header> = {
}
export const ByStudent: ComponentStoryObj<typeof Header> = {
}

global decoratorの例

Storybookの機能として、preview.js(tsx)にてProviderでラップする処理をdecoratorsに指定する方法がUIフレームワーク利用の際に主に使われますが、今回はこのストーリーにおいてのみProviderを適用したいので、向いていないです。

const withChakra = (StoryFn: Function) => {
  return (
    <ChakraProvider theme={theme}>
      <StoryFn />
    </ChakraProvider>
  );
};

export const decorators = [withChakra]; // ここに書き足すと、全ストーリーに同じProviderが強制的に適用される

対応方法

実はストーリー単位でdecoratorsを設定できるため、以下のようにStoryをProviderで囲うことで対応できます。

export const ByTeacher: ComponentStoryObj<typeof Header> = {
  decorators: [
    (Story) => {
      return (
        <AuthContext.Provider
          value={{
            user: {
              type: 'teacher',
              name: 'taro'
            },
            dispatch: () => undefined as any,
          }}
        >
          <Story />
        </AuthContext.Provider>
      );
    },
  ],
};

この方法だとストーリー単位で別々の値をProviderから流し込むことができるので、コンポーネント内部の出し分けを容易に再現可能です。

Pros/Cons

このように各ストーリーでdecoratorsに自前のContextを指定するメリットは、コンポーネント設計の粒度が大きくて、Contextと密結合していることが多くても問題なくStorybookで再現できることです。

デメリットとしては、そもそもコンポーネントをContextを受け取るContainer役のコンポーネントと、純粋にPropsから受け取ったデータを表示するPresentation役のコンポーネントに切り分けていれば本記事の工夫は不要のため、雑なコンポーネント設計を助長する可能性があります。

Reference

https://storybook.js.org/docs/react/writing-stories/decorators#component-decorators

Discussion