DEV Community

Origami
Origami

Posted on

2 2

HoCとStorybook/addon-infoの落とし穴

TL;DR

Higher-Order Componentsをaddon-infoで表示させようとするとバグる

Storybook? Storybook/addon-info?

みなさん、storybookを使ってますか?コンポーネントを作っていく時に便利すぎるので是非使ってください。説明だるいのでプロジェクトページのexamples見ればすぐわかりの取得ができます。

さて、storybookのプラグインであるaddon-infoも超便利です。コンポーネントがどのような役割を果たすのか、何を意図して作られたのかをmarkdownで記述すると表示してくれますし、実際にstoryのコード内でどのように使われているかを表示したり、Flowによる型チェックが(たぶんTypeScriptも)定義されていた場合、それも表示してくれます。

たとえば、以下のようなコンポーネントがあるとします。

// @flow
import React from 'react';

type Props = {
    /* クエスチョンマークの前につく文字列です */
    label: string,
    /* クエスチョンマークの数 */
    amount: number,
};

/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
    <a>{label + ''.repeat(amount)}</a>
);

SuperQuestionLabel.defaultProps = {
    label: "",
};

export default SuperQuestionLabel;

Enter fullscreen mode Exit fullscreen mode

そして、story用のコードは次のようになります。

// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';

import SuperQuestionLabel from "../SuperQuestionLabel";

storiesOf('何もわからん', module)
    .add(
        'basic usage',
        withInfo(
            '感動する文章'
        )(() => (
            <SuperQuestionLabel
                label={"これからは飲酒の時代"}
                amount={8} />
        )))


Enter fullscreen mode Exit fullscreen mode

すると、storybookの当該ページにはInfoボタンが表示され、クリックすると次のようなすばらしいインフォメーション情報が表示されます。

感動する文章、コンポーネントがどのように使われるのかの一例、そしてプロパティの詳細情報、感動ですね。Reactに限らず、今やReactコンポーネントを作るのであればstorybook、そしてaddon-infoは欠かせないものになりつつあります。(残念な点としては、今の所addon-infoはReactにしか対応してない点でしょうか)

Main Issue

そんなaddon-infoには天敵が存在します。Higher-Order Componentsです。

その例として、Stateless Functional Componentsで、recomposeの機能を使用している場合です。

うまくいかない例を見てみましょう。

// @flow
import React from 'react';
import {pure} from 'recompose';

type Props = {
    /* クエスチョンマークの前につく文字列です */
    label: string,
    /* クエスチョンマークの数 */
    amount: number,
};

/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
    <a>{label + ''.repeat(amount)}</a>
);

SuperQuestionLabel.defaultProps = {
    label: "",
};

export default pure(SuperQuestionLabel);

Enter fullscreen mode Exit fullscreen mode

recomposeのpureを用いてコンポーネントの再レンダリングを抑えています。高いパフォーマンスを求められるWebアプリケーションであれば、pureonlyUpdateForKeysを用いてチューニングを行うことも多々あるでしょう。しかし、StorybookのInfoページは次のようになってしまいます。

感動どころか、失望してしまいます。

なぜこのようになるか?理由はこれらのpureonlyUpdateForKeysHigher-Order Componentsだからです。するとひとつコンポーネントにコンポーネントがラップされた形になってしまいますから、そのためにaddon-infoが各種情報を拾ってくれなくなるのです。

Solution

HoCしたコンポーネントであることが問題なのですから、素のコンポーネントをstoriesに記述すればいいわけです。

つまり、次のようなコードにします。

// @flow
import React from 'react';
import {pure} from 'recompose';

type Props = {
    /* クエスチョンマークの前につく文字列です */
    label: string,
    /* クエスチョンマークの数 */
    amount: number,
};

/* めっちゃクエスチョンマークをあれします */
const SuperQuestionLabel = ({label, amount}: Props) => (
    <a>{label + ''.repeat(amount)}</a>
);

SuperQuestionLabel.defaultProps = {
    label: "",
};

export const SuperQuestionLabel_ = SuperQuestionLabel;
export default pure(SuperQuestionLabel);
Enter fullscreen mode Exit fullscreen mode

export constを使用して、素のコンポーネントを追加で出力するようにしただけです。

そして、story用のコードも、importするものを変更すればいいのです。

// @flow
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';

// ここを変更!
import {SuperQuestionLabel_} from "../SuperQuestionLabel";

storiesOf('すべてが理解できた', module)
    .add(
        'basic usage',
        withInfo(
            '感動する文章'
        )(() => (
            <SuperQuestionLabel_
                label={"これからは飲酒の時代"}
                amount={8} />
        )))

Enter fullscreen mode Exit fullscreen mode

少々(exportする名前が)雑ではありますが、これでaddon-infoは次のように、期待通りの表示をしてくれます。

感動!君も泣け

Conclusion

Higher-Order Componentsをdefaultで出力しているコンポーネントは、少なくともStorybookのaddon-infoで概要を表示させたいのであれば、素のコンポーネントもexportしてあげよう

END OF FILE

最後の画像のAlt textは「感動!君も泣け」です。

そして私はこの問題におもいっきりハマり、2日無駄にしました。人生は短く、みなさんの人生も短い、だから私は人生の時間をさらに削り、みなさんの人生の時間を無駄にしないよう、記事を書いている…

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay