ReactのHOCでModalを実装

ReactでModalを実装するときHigher Order Componentを用いたのでその備忘録になります。

## Higher Order Component(HOC)って

[公式サイト](https://ja.reactjs.org/docs/higher-order-components.html)も分かりやすいですが、qiitaの記事を参考にするならこの記事がわかりやすかったです。「[React の Higher-order Components の利用方法](https://qiita.com/numanomanu/items/2b66d8b2887d44f857dc)」

注意してほしいのは公式にも書かれているとおり、componentを受け取って新規のcomponenを返す、単なる関数です。設計パターンです、
なので、なにかライブラリを使うわけではないです。

> 高階コンポーネント (higher-order component; HOC) はコンポーネントのロジックを再利用するための React における応用テクニックです。HOC それ自体は React の API の一部ではありません。HOC は、React のコンポジションの性質から生まれる設計パターンです。

> 具体的には、高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数です。

``` javascript
const enhance = compose<ImplProps, ImplOuterProps>(ModalHoc);
const Sample = enhance(SampleModal);
```

こんな感じでHOCの引数に毎回中身が違うmodalのcomponentを渡して、毎回同じ処理をするHOC側でModalのcomponentを作るということです。

このときポイントとなるのが下記になると思います。

- HOC側でReduxの処理をする
- HOCを呼ぶ側のcomponentにはpropsを取得するだけで処理を分離させる

## 実際に書いてみた

- Material-uiのModalを使ってます。
- TypeScriptで書いてます

### 毎回変わるModalの中身のコンポーネント

- modalの中身に渡したいdataをpropsのmodalValueで管理しています。たとえばUserIDとか

```javascript:Sample.tsx
const SampleModal: FC<ImplFunctionComponent> = props => {
  const { modalValue, hideModal } = props;

  return (
    <>
      <h2 id="transition-modal-title">タイトルが入るよ</h2>
      <p id="transition-modal-description">
        {modalValue ? modalValue.userId : 'IDないってよ'}
      </p>
      <Button
        variant="contained"
        color="secondary"
        type="button"
        onClick={hideModal}>
        Close
      </Button>
    </>
  );
};

const enhance = compose<ImplFunctionComponent, ImplOuterProps>(ModalHoc);
const Sample = enhance(SampleModal);

export default Sample;
```

HOCとは関係ないのですが、composeの型を定義するのに四苦八苦しました。

```
compose<InnerProps, OuterProps>
```
で指定します。`InnerProps`はこのSampleコンポーネントの型を指定。`OuterProps`はHOCコンポーネントの型を指定となります。

このSampleコンポーネントが毎回変わるModalの中身を書くコンポーネントなのでHOCのことは気にせずにガシガシ書いていけばいいわけです。
propsの`hideModal`関数でmodalを閉じる処理が行われます。

### ModalのHOCコンポーネント

- このコンポーネントにReduxでデータを取得、dispatchする処理を書いていきます。
- それらの処理を子コンポーネント(Sampleコンポーネント)に渡します
- Material-UIの記述部分を省略しています。

```javascript:ModalHoc.tsx
export interface ImplOuterProps {
  modalName: string;
}

interface ImplInnerProps {
  isOpen: boolean;
  modalValue?: {};
  handleClose: () => void;
}

type ImplProps = ImplInnerProps & ImplOuterProps;

export interface ImplFunctionComponent {
  modalValue?: object;
  hideModal: () => void;
}

const ModalHoc = (FunctionComponent: FC<ImplFunctionComponent>) => {
  const ModalWrapper: FC<ImplProps> = props => {
    const {
      modalName,
      isOpen,
      modalValue,
      handleClose
    } = props;

    return (
      <Modal
        open={isOpen}
        onClose={handleClose}
        <Fade in={isOpen}>
          <div>
            <ComposedComponent
              modalValue={modalValue}
              hideModal={handleClose}
            />
          </div>
        </Fade>
      </Modal>
    );
  };

  const mapStateToProps = (
    state: ImplState,
    { modalName }: { modalName: string }
  ) => {
    const targetModal = state.modal[modalName];
    return {
      isOpen: targetModal ? targetModal.isOpen : false,
      modalValue: targetModal ? targetModal.modalValue : {}
    };
  };

  const mapDispatchToProps = (
    dispatch: Dispatch<ImplModalAction>,
    { modalName }: { modalName: string }
  ) => ({
    handleClose: () => dispatch(hideModal(modalName))
  });

  const enhance = compose<ImplProps, {}>(
    connect(mapStateToProps, mapDispatchToProps)
  );

  return enhance(ModalWrapper);
};

export default ModalHoc;
```

```
const ModalHoc = (FunctionComponent: FC<ImplFunctionComponent>)
```
この`ComposedComponent`が先程書いたSampleコンポーネントになります。
Sampleコンポーネントのpropsに渡したいデータ(modalValue)、処理(modalを閉じる処理)を書いています。

```
<ComposedComponent
  modalValue={modalValue}
  hideModal={handleClose}
/>
```

ちょっと難解なのがこのHOCのなかで`ModalWrapper`というコンポーネントを作っています。そしてそれをenhanceでwrapした関数を返したもののがHOCになります。
あとは先程説明したようにこのHOC側で`mapStateToProps,mapDispatchToProps`を使ってstateのdataを取得したり、reducerの処理を行っています。

### Modalを配置するコンポーネント

- Sampleコンポーネントを呼ぶ

```javascript:pages/index.tsx
<Sample
  modalName={SAMPLE_MODAL}
/>
```

ここでのポイントはここで指定したpropsがHOCコンポーネントのpropsに入ることですね。

以上になります。
HOCを使うとコンポーネントを再利用できるのと、Reducer処理とただコーディングするだけとで分けられるからいいですね。