HoCとStorybook/addon-infoの落とし穴
Tags: react, storybook, japanese
Published: 2018-07-19 15:36:26

---
title: HoCとStorybook/addon-infoの落とし穴
published: true
description: HoCとStorybook/addon-infoの相性は最悪
tags: react, storybook, japanese
---

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

# Storybook? Storybook/addon-info?
みなさん、[storybook](https://storybook.js.org)を使ってますか？コンポーネントを作っていく時に便利すぎるので是非使ってください。説明だるいのでプロジェクトページのexamples見ればすぐわかりの取得ができます。  
  
さて、storybookのプラグインである[addon-info](https://www.npmjs.com/package/@storybook/addon-info)も超便利です。コンポーネントがどのような役割を果たすのか、何を意図して作られたのかをmarkdownで記述すると表示してくれますし、実際にstoryのコード内でどのように使われているかを表示したり、Flowによる型チェックが(たぶんTypeScriptも)定義されていた場合、それも表示してくれます。  
  
たとえば、以下のようなコンポーネントがあるとします。

```jsx
// @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;

```

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

```jsx
// @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} />
		)))


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

![](https://thepracticaldev.s3.amazonaws.com/i/zr3smtqm77t89bgb8btv.png)

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

そんなaddon-infoには天敵が存在します。**Higher-Order Components**です。  
  
その例として、Stateless Functional Componentsで、[recompose](https://github.com/acdlite/recompose)の機能を使用している場合です。  
うまくいかない例を見てみましょう。  

```jsx
// @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);

```

recomposeの`pure`を用いてコンポーネントの再レンダリングを抑えています。高いパフォーマンスを求められるWebアプリケーションであれば、`pure`や`onlyUpdateForKeys`を用いてチューニングを行うことも多々あるでしょう。しかし、StorybookのInfoページは次のようになってしまいます。  
  
![](https://thepracticaldev.s3.amazonaws.com/i/ycinss8x5syqm1yvodaz.png)

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

# Solution
HoCしたコンポーネントであることが問題なのですから、素のコンポーネントをstoriesに記述すればいいわけです。  
つまり、次のようなコードにします。  

```jsx
// @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);
```

`export const`を使用して、素のコンポーネントを追加で出力するようにしただけです。  
そして、story用のコードも、importするものを変更すればいいのです。  

```jsx
// @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} />
		)))

```

少々(exportする名前が)雑ではありますが、これでaddon-infoは次のように、期待通りの表示をしてくれます。  
  
![感動！君も泣け](https://thepracticaldev.s3.amazonaws.com/i/qni06of2tjduwhimlyh7.png)

# Conclusion
Higher-Order Componentsをdefaultで出力しているコンポーネントは、少なくともStorybookのaddon-infoで概要を表示させたいのであれば、素のコンポーネントもexportしてあげよう  
  
## END OF FILE
最後の画像のAlt textは「感動！君も泣け」です。  
そして私はこの問題におもいっきりハマり、2日無駄にしました。人生は短く、みなさんの人生も短い、だから私は人生の時間をさらに削り、みなさんの人生の時間を無駄にしないよう、記事を書いている…