💡

Micromodal.jsをカスタムHookで仕立てる

2021/12/01に公開

JSON色付け係のhanetsukiです。
今回はMicromodal.jsをカスタムHookで汎用的に使えるようにしようという試みで記事を書きました。

今回の実装はリポジトリを公開してありますので、参考になれば幸いです。
https://github.com/tsuki-lab/example-use-micromodal

Micromodal.jsとは

  • バニラjsで構成されたモーダルライブラリ
  • アクセシビリティが考慮されている
  • 1.9kbと軽量

要点をまとめると上記のような特徴を持っています。

近年様々なフレームワークを元に実装して行く最中、どのフレームワークに対してもアプローチ可能で、軽量!
そして、一番実装で苦戦してしまいそうなa11yが考慮されているのは非常にありがたいです。

事前準備

インストール

micromodalをプロジェクトに追加します

yarn add micromodal

useMicromodalを作成する

今回はuse-micromodalフォルダを作成し、その中に4つのファイルで振り分けたいと思います。

  • index.tsx
    useMicromodalのエントリーポイントとなるファイル
  • modal.view.tsx
    Micromodal.jsの描画を司るファイル
  • modal.hook.ts
    Micromodal.jsの機能を司るファイル
  • modal.scss
    modal.view.tsxの装飾を司るファイル

では、早速作成していきましょう!

modal.view.tsx

公式のUsageに従ってマークアップしていきます。

hooks/use-micromodal/modal.view.tsx
import styles from './modal.module.scss'
import { createPortal } from 'react-dom'

type Props = {
  id: string
}

export const Modal: React.FC<Props> = ({ children, id }) => {
  return createPortal(
    <div id={id} aria-hidden="true" className={styles.wrap}>
      <div className={styles.overlay} tabIndex={-1} data-micromodal-close>
        <div role="dialog" className={styles.dialog} aria-modal="true">
          {children}
        </div>
      </div>
    </div>,
    document.body
  )
}

この時ポイントなのは、createPortalを用いることです。

https://ja.reactjs.org/docs/portals.html

モーダルコンポーネントを通常のコンポーネントと同じように作成してしまうと、z-indexなどのDOMの階層による表示バグの原因となります。
createPortal を用いることで、<body>タグの閉じタグ直前にappendする形でモーダルコンポーネントを描画することが可能になります。

modal.scss

modal.view.tsxの装飾をしていきます。

hooks/use-micromodal/modal.module.scss
.wrap {
  display: none;
  &.is-open {
    display: block;
  }
}

.overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.dialog {
  overflow-y: auto;
  max-height: 100vh;
}

ポイントは&.is-open { display: block; }の記述です。
Micromodal.jsでは、対象となるidのDOMにis-openのクラスを付与することで描画をコントロールしています。
※ このクラスはオプションで変更可能です。

modal.hook.ts

モーダルの開閉とMicromodal.jsを初期化する処理を記述します。

hooks/use-micromodal/modal.hook.ts
import client from 'micromodal'
import { useCallback } from 'react'

export const useHook = (id: string) => {
  const open = useCallback(() => {
    client.show(id, {
      disableScroll: true
    })
  }, [id])

  const close = useCallback(() => {
    client.close(id)
  }, [id])

  return { open, close }
}

client.showの第二引数にmicromodalのオプションを渡すことができます。

https://micromodal.vercel.app/#configuration

client.show({
    disableScroll: true
})

index.tsx

最後にエントリーポイントです。
今まで作成したviewとhookを組み合わせます。

hooks/use-micromodal/index.tsx
import { useHook } from './modal.hook'
import { Modal as ModalComponent } from './modal.view'
import { useCallback } from 'react'

export type UseMicromodal = (id: string) => {
  Modal: React.FC<{ children: React.ReactNode }>
  open: () => void
  close: () => void
}

export const useMicromodal: UseMicromodal = (id: string) => {
  const { open, close } = useHook(id)

  const Modal = useCallback(
    ({ children }) => {
      return <ModalComponent id={id}>{children}</ModalComponent>
    },
    [id]
  )

  return {
    Modal,
    open,
    close
  }
}

ポイントはカスタムHooksの内部でコンポーネントを生成し、返却する点です。
Provider形式にするか迷いましたが、今回はこの形で作成してみました。

このファイルを起点としてuseMicromodalを呼ぶことが可能になりました。

利用方法

App.tsx
import { useMicromodal } from './hooks/use-micromodal'

function App() {
  const { Modal, open, close } = useMicromodal('sample-modal')

  return (
    <div className="App">
      <button onClick={open}>open</button>

      <Modal>
        <p>Opend modal</p>
        <button onClick={close}>close</button>
      </Modal>
    </div>
  )
}

利用したい場所でuseMicromodalを呼び出し、引数にmodalのIDとなる文字列を渡して実行します。
戻り値にメソッドとコンポーネントが返るので、それを利用してあとはよしなにコーディングするだけです。

カスタムHookを用いて利用時にスッキリと利用できるコンポーネントが出来上がりました!!

最後に

アクセシビリティなど、webアプリケーションを作成する上でなかなか行き届かないところをライブラリを使い開発するのが主流になってきました。

こういったラップして使うことが多くなって行くんだろうなというのが最近思ったことです。
うまくライブラリとは付き合っていきたいですね!

至らぬ点などあったとは思いますが、最後まで読んでいただき有難うございました。

Discussion