StorybookでEmotionのThemeをProvideする
Next.jsとEmotionの構成で開発しているプロジェクトにてStorybookを導入しようということになったので、いざやってみようとしてみたところTheme周りでエラーが出たのでまとめてみました。
構成
react -> 18.2.0
next -> 12.1.6
emotion/react -> 11.9.3
storybook/react -> 6.5.9
例えばこんなコンポーネントがあったとして...
import { FC, ReactNode } from 'react'
import { css, Theme } from '@emotion/react'
import { Typography } from '@mui/material'
type Props = {
children?: ReactNode
}
const AppText: FC<Props> = ({ children }) => {
return <Typography css={textStyle}>{children}</Typography>
}
const textStyle = (theme: Theme) => css`
color: ${theme.palette.text.secondary};
`
export default AppText
AppText
自体に特に意味はないですが、ここで重要なのはtextStyle
の中の
color: ${theme.palette.text.secondary};
のthemeを使っている部分です。
で、このthemeをProvideしているのが_app.tsx
で
import '../styles/globals.css'
import { ReactElement, ReactNode } from 'react'
import type { AppProps } from 'next/app'
import { ThemeProvider } from '@emotion/react'
import { ThemeProvider as MUThemeProvider } from '@mui/material/styles'
import theme from '../theme'
import { NextPage } from 'next'
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
return (
<MUThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</MUThemeProvider>
)
}
export default MyApp
EmotionのThemeProvider
とMUIのThemeProvider
の名前が被っているので別名でimportして、それぞれで定義したthemeをprovideするようにしています。
こうすることで、アプリを起動した時にそれぞれのコンポーネントにthemeが提供され、各コンポーネントにて使用できるようになります
ここまで説明すると、薄々答えが見えてきた方もいると思いますが、Storybookの起動時はこのthemeを別途Provideする必要があるため、そのままStorybookを起動してしまうとtheme周りのエラーがでます。
Storybook
というわけで本編です。
AppText.tsx
のstoriesがこんな感じになります。
import { ComponentMeta } from '@storybook/react'
import AppText from './AppText'
export default {
title: 'Components / Atoms / AppText',
component: AppText,
} as ComponentMeta<typeof AppText>
export const Default = () => <AppText>Hoge</AppText>
この状態でStorybookを起動すると以下のようなエラーが出ます
Cannot read properties of undefined (reading 'text')
エラー自体はよくみるやつで、要は
undefinedにtextってやつはないよ
って言われていて、先ほど言ったようにStorybookではthemeをProvideしていないので、このようなエラーがでます。
じゃあthemeをProvideしようかとなるのですが、方法がいくつかあります。
方法1
方法1は各Storyにdecorators
というものを定義する方法です
先ほどのStoryを以下のように修正します。
import { ComponentMeta } from '@storybook/react'
import AppText from './AppText'
export default {
title: 'Components / Atoms / AppText',
component: AppText,
// 追加
decorators: [
(Story) => (
<MUThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
</MUThemeProvider>
),
],
// ここまで
} as ComponentMeta<typeof AppText>
export const Default = () => <AppText>Hoge</AppText>
Storyの設定ではdecorators
というものが設定でき、ここでthemeをProvideするようにすれば、実際にStorybookを起動した際にも、themeを提供することができます。
Storybookのdecorators
自体は色んな用途で使用されるのですが、今回はThemeをProvideするという用途で使用しています
ここまでが方法1なのですが、この方法の問題点としてはコンポーネントごとにdecorators
を設定する必要があり、例えば今回のように不特定多数のコンポーネントでthemeが使用されるような場合だと、わざわざ使用されるStoryファイル全部にdecorators
を書かないといけなくなります
まあ、そもそもコンポーネントの数が少ないとかだとそれでもいいんですが、対応漏れや冗長であることからGlobalな感じでdecorators
を設定できると良いですね
ということで方法2です
方法2
といっても、書く場所が違うだけで、書くことはほぼ同じなのですが、、、
.storybook/preview.js
という場所(なければ作ります)に以下のコードを追加します
// 略
export const decorators = [
(Story) => (
<MUThemeProvider theme={theme}>
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
</MUThemeProvider>
),
]
方法1で書いたものと全く同じですが、preview.js
にてdecorators
として変数をexportすることで全Storyファイルにこのdecorators
が適用されます。
こうすることで、わざわざ新しいコンポーネントを作ってStoryを定義しようとした時に、ThemeProviderやthemeをimportしてdecorators
に書く必要がなくなりました!
まとめ
今まで何度かStorybookを使用したことはあったのですが、今回初めてEmotionなどを含めた構成をはじめから構築したので、色んなところで躓きましたがいい経験になりました
結構Theme系は調べると奥が深いのでこれを機にもっと知識を深めていきたいですね
Discussion