Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

Next.jsのアプリケーションにnice-modal-reactを導入してみる

参照したページ

今日の環境

  • ebay/nice-modal-react: 1.2.10
  • next: 13.4.4

はじめに

Next.jsのアプリケーションにnice-modal-reactを使って、実用的なモーダルを実装していきます。

Next.jsのApp Routerは使わないアプリケーションにnice-modal-reactを導入する例です。

nice-modal-reactをインストールする

以下のコマンドを実行しました。

yarn add @ebay/nice-modal-react

src/components/Modal.tsxの作成

Modalコンポーネントを作成しました。
汎用的に使用するモーダルの枠です。CSSはstyled-jsxで書いています。

以下の内容で作成しました。通常のコンポーネントと違って、NiceModal.create()の戻り値をexportしています。

import React from "react";
import css from "styled-jsx/css";
import * as NiceModal from "@ebay/nice-modal-react";

type Props = {
  children: React.ReactNode;
  isVisible: boolean;
  onOverlayClick: () => void;
};

export const Modal = NiceModal.create(function Modal({
  children,
  isVisible,
  onOverlayClick,
}: Props) {
  const [modifierClassName, setModifierClassName] = React.useState("");

  React.useEffect(() => {
    const modifierClassName = isVisible ? "is-visible" : "";
    setModifierClassName(modifierClassName);
  }, [isVisible]);

  return (
    <div className={`modal ${modifierClassName}`}>
      <div className="modal-overlay" onClick={onOverlayClick}>
        &nbsp;
      </div>
      {children}
      <style jsx>{styles}</style>
    </div>
  );
});

const styles = css`
  .modal {
    position: fixed;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.5s;
  }

  .modal.is-visible {
    pointer-events: initial;
    opacity: 1;
  }

  .modal-overlay {
    position: fixed;
    width: 100vw;
    height: 100vh;
    background-color: #000;
    opacity: 0.5;
  }
`;

src/components/ExampleModalContents.tsxの作成

ExampleModalContentsコンポーネントを以下の内容で作成しました。

import React from "react";
import css from "styled-jsx/css";

type Props = {
  children: React.ReactNode;
};

export const ExampleModalContents = React.memo(function ExampleModalContents({
  children,
}: Props) {
  return (
    <div className="example-modal-contents">
      {children}
      <style jsx>{styles}</style>
    </div>
  );
});

const styles = css`
  .example-modal-contents {
    position: fixed;
    width: 80vw;
    height: 80vh;
    background-color: #fff;
  }
`;

src/pages/_app.tsx の編集

src/pages/_app.tsxには、個々の環境によって、色々書いてあるとは思いますが、以下のことをします。

  • NiceModalをimport
  • <NiceModal.Provider>でwrapする

例としては以下のようになります。

import type { AppProps } from "next/app";
import * as NiceModal from "@ebay/nice-modal-react";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <NiceModal.Provider>
      <Component {...pageProps} />
    </NiceModal.Provider>
  );
}

src/pages/index.tsx の編集

モーダルを使用する場所はどこでも良いのですが、例として src/pages/index.tsx を以下のように編集しました。
useModal()の引数とModalコンポーネントのid属性には同じ文字列を渡します。

import * as NiceModal from "@ebay/nice-modal-react";
import { Modal } from "@/components/Modal";
import { ExampleModalContents } from "@/components/ExampleModalContents";

export default function Home() {
  const modalId = "example-modal";
  const modal = NiceModal.useModal(modalId);

  const showModal = () => {
    void modal.show();
  };

  const onCancelButtonClick = () => {
    void modal.hide();
  };

  const onApplyButtonClick = () => {
    void modal.hide();
  };

  return (
    <div>
      <button onClick={showModal}>モーダルを開く</button>

      <Modal
        id={modalId}
        isVisible={modal.visible}
        onOverlayClick={onCancelButtonClick}
      >
        <ExampleModalContents>
          <button onClick={onCancelButtonClick}>キャンセル</button>
          <button onClick={onApplyButtonClick}>適用</button>
        </ExampleModalContents>
      </Modal>
    </div>
  );
}

modal.show()のPromiseを使いたい場合

例えば以下のようにすると、modal.show() が返している Promise を使って後続の処理を実行することもできます。

  const showModal = () => {
    modal
      .show()
      .then((result) => {
        console.log('result: ', result);
      })
      .catch((error) => {
        console.log('error: ', error);
      });
  };

  const onOverlayClick = () => {
    modal.resolve({ message: 'resolved foo' });
    void modal.hide();
  };