🎄

Reactでモーダルを実装してみた

2022/12/04に公開

これは ZOZO Advent Calendar 2022 カレンダー Vol.4 の 4 日目の記事です。

表題の通り、Reactでモーダルを実装するということで、CodeSandboxを使って開発していきます。
今回は初心者向けに基本的な機能のみ実装しています。🙇‍♂️
参考にするのはChakra UIです。

完成したコードだけみたい人はこちらを見て下さい!
それでは早速CodeSandboxでテンプレートを用意しましょう!

テンプレート作成

手順

1.左上のCreateをクリック

2.Reactをクリック

3.Hello CodeSandboxが出たら準備完了

モーダル作成

Reactを動かせる環境が用意できたということで次はモーダルの実装に入っていきましょう!

1.モーダルに必要なUIの作成

UIはChakra UIを参考にします。

手順

1.Componetnts/Modal.jsxファイルを作成

2.下記コードをコピペ

Modal.jsx
export const Modal = () => {
  return (
    <div className="container">
      <section className="containerInner">
        <h2 className="header">Modal title</h2>
        <button
          type="button"
          aria-label="閉じる"
          className="iconClose"
        >
          ×
        </button>
        <p className="contents">
	  Sit nulla est ex deserunt exercitation anim occaecat. Nostrud ullamco
          deserunt aute id consequat veniam incididunt duis in sint irure nisi.
          Mollit officia cillum Lorem ullamco minim nostrud elit officia tempor
          esse quis. Sunt ad dolore quis aute consequat. Magna exercitation
          reprehenderit magna aute tempor cupidatat consequat elit dolor
          adipisicing. Mollit dolor eiusmod sunt ex incididunt cillum quis.
          Velit duis sit officia eiusmod Lorem aliqua enim laboris do dolor
          eiusmod. Et mollit incididunt nisi consectetur esse laborum eiusmod
          pariatur proident Lorem eiusmod et. Culpa deserunt nostrud ad veniam.
        </p>
        <div className="buttonContainer">
          <button
            type="button"
            className="button closeButton"
          >
            close
          </button>
          <button type="button" className="button nextButton">
            Secondary Action
          </button>
        </div>
      </section>
    </div>
  );
};


App.js
- <div className="App">
-     <h1>Hello CodeSandbox</h1>
-     <h2>Start editing to see some magic happen!</h2>
-  </div>
+  <div className="App">
+    <button type="button" className="button openButton">
+      Open Modal
+    </button>
+      <Modal/>
+  </div>
style.css
body,
h2,
p {
  padding: 0;
  margin: 0;
}
* {
  box-sizing: border-box;
}

.header {
  font-size: 20px;
  padding: 16px 24px;
}

.App {
  min-height: 200vh;
  background-color: greenyellow;
}

.buttonContainer {
  display: flex;
  justify-content: flex-end;
  padding: 16px 24px;
}

.contents {
  padding: 8px 24px;
}

.containerInner {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: #fff;
  width: 462px;
  border: 1px solid black;
  border-radius: 10px;
}

.button {
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 16px;
  cursor: pointer;
  border: none;
}

.openButton {
  background-color: #edf2f7;
}

.openButton:hover {
  background-color: #e2e8f0;
}

.closeButton {
  background-color: #3182ce;
  color: #fff;
}

.closeButton:hover {
  background-color: #2b6cb0;
}

.nextButton {
  background-color: transparent;
  margin-left: 4px;
}

.nextButton:hover {
  background-color: #e2e8f0;
}

.iconClose {
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  top: 10px;
  right: 10px;
  background-color: transparent;
  border: none;
  font-size: 30px;
  height: 30px;
  width: 30px;
  border-radius: 10px;
  cursor: pointer;
}

.iconClose:hover {
  background-color: #e2e8f0;
}

このようになると思います。

2.モーダルの機能を追加

手順

開閉機能の追加

1.Componetnts/useModal.jsxファイルを作成

2.カスタムフックを作成

useModal.jsx
import { useCallback, useState } from "react";

export const useModal = () => {
  const [isOpen, setIsOpen] = useState(false);

  const onOpen = useCallback(() => {
    setIsOpen(true);
  }, [setIsOpen]);

  const onClose = useCallback(() => {
    setIsOpen(false);
  }, [setIsOpen]);

  return { isOpen, onOpen, onClose };
};

3.カスタムフックを使って開閉機能を実装

App.js
export default function App() {
+ const { isOpen, onClose, onOpen } = useModal();
  return (
    <div className="App">
+   <button onClick={onOpen} type="button" className="button openButton">
        Open Modal
      </button>
+     {isOpen && <Modal onClose={onClose} />}
    </div>
  );
}
Modal.jsx
+ export const Modal = ({ onClose }) => {
  return (
    <div className="container">
      <section className="containerInner">
        <h2 className="header">Modal title</h2>
        <button
+         onClick={onClose}
          type="button"
          aria-label="閉じる"
          className="iconClose"
        >
          ×
        </button>
        <p className="contents">
          Sit nulla est ex deserunt exercitation anim occaecat. Nostrud ullamco
          deserunt aute id consequat veniam incididunt duis in sint irure nisi.
          Mollit officia cillum Lorem ullamco minim nostrud elit officia tempor
          esse quis. Sunt ad dolore quis aute consequat. Magna exercitation
          reprehenderit magna aute tempor cupidatat consequat elit dolor
          adipisicing. Mollit dolor eiusmod sunt ex incididunt cillum quis.
          Velit duis sit officia eiusmod Lorem aliqua enim laboris do dolor
          eiusmod. Et mollit incididunt nisi consectetur esse laborum eiusmod
          pariatur proident Lorem eiusmod et. Culpa deserunt nostrud ad veniam.
        </p>
        <div className="buttonContainer">
          <button
+           onClick={onClose}
            type="button"
            className="button closeButton"
          >
            close
          </button>
          <button type="button" className="button nextButton">
            Secondary Action
          </button>
        </div>
      </section>
    </div>
  );
};

スクロールの制御機能を追加

1.背景の追加

Modal.jsx
export const Modal = ({ onClose }) => {
  return (
    <div className="container">
+      <div className="overlay" onClick={onClose} />
      <section className="containerInner">
        <h2 className="header">Modal title</h2>
        <button
          onClick={onClose}
          type="button"
          aria-label="閉じる"
          className="iconClose"
        >
          ×
        </button>
styles.css
.overlay {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.4);
}

このようになります!
今のままだとモーダルが開いた状態でもスクロールできてしまうので、
これからスクロールを制御しましょう。

2.モーダルが開いている時に下のコンテンツが動かないようにする

今回はoverscroll-behaviorプロパティを使ってスクロールを制御します。
ついでにモーダルを中央揃えにしましょう。

styles.js
.overlay {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.4);
+  overscroll-behavior: contain;
+  overflow-y: scroll;
}

.container {
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100%;
  top: 0;
  left: 0;
  overscroll-behavior: contain;
  overflow-y: scroll;
}

次にコンテンツ部分に長い文章が入った場合にコンテンツの中身をスクロールできるようにしましょう!

まずモーダルのコンテンツ部分に長い文章を入れましょう。

 <p className="contents">
          Sit nulla est ex deserunt exercitation anim occaecat. Nostrud ullamco
          deserunt aute id consequat veniam incididunt duis in sint irure nisi.
          Mollit officia cillum Lorem ullamco minim nostrud elit officia tempor
          esse quis. Sunt ad dolore quis aute consequat. Magna exercitation
          reprehenderit magna aute tempor cupidatat consequat elit dolor
          adipisicing. Mollit dolor eiusmod sunt ex incididunt cillum quis.
          Velit duis sit officia eiusmod Lorem aliqua enim laboris do dolor
          eiusmod. Et mollit incididunt nisi consectetur esse laborum eiusmod
          pariatur proident Lorem eiusmod et. Culpa deserunt nostrud ad veniam.
          Est velit labore esse esse cupidatat. Velit id elit consequat minim.
          Mollit enim excepteur ea laboris adipisicing aliqua proident occaecat
          do do adipisicing adipisicing ut fugiat. Consequat pariatur ullamco
          aute sunt esse. Irure excepteur eu non eiusmod. Commodo commodo et ad
          ipsum elit esse pariatur sit adipisicing sunt excepteur enim.
          Incididunt duis commodo mollit esse veniam non exercitation dolore
          occaecat ea nostrud laboris. Adipisicing occaecat fugiat fugiat irure
          fugiat in magna non consectetur proident fugiat. Commodo magna et
          aliqua elit sint cupidatat. Sint aute ullamco enim cillum anim ex. Est
          eiusmod commodo occaecat consequat laboris est do duis. Enim
          incididunt non culpa velit quis aute in elit magna ullamco in
          consequat ex proident. Dolore incididunt mollit fugiat pariatur
          cupidatat ipsum laborum cillum. Commodo consequat velit cupidatat duis
          ex nisi non aliquip ad ea pariatur do culpa. Eiusmod proident
          adipisicing tempor tempor qui pariatur voluptate dolor do ea commodo.
          Veniam voluptate cupidatat ex nisi do ullamco in quis elit. Cillum
          proident veniam cupidatat pariatur laborum tempor cupidatat anim
          eiusmod id nostrud pariatur tempor reprehenderit. Do esse ullamco
          laboris sunt proident est ea exercitation cupidatat. Do Lorem eiusmod
          aliqua culpa ullamco consectetur veniam voluptate cillum. Dolor
          consequat cillum tempor laboris mollit laborum reprehenderit
          reprehenderit veniam aliqua deserunt cupidatat consequat id. Est id
          tempor excepteur enim labore sint aliquip consequat duis minim tempor
          proident. Dolor incididunt aliquip minim elit ea. Exercitation non
          officia eu id. Ipsum ipsum consequat incididunt do aliquip pariatur
          nostrud. Qui ut sint culpa labore Lorem. Magna deserunt aliquip aute
          duis consectetur magna amet anim. Magna fugiat est nostrud veniam.
          Officia duis ea sunt aliqua. Ipsum minim officia aute anim minim aute
          aliquip aute non in non. Ipsum aliquip proident ut dolore eiusmod ad
          fugiat fugiat ut ex. Ea velit Lorem ut et commodo nulla voluptate
          veniam ea et aliqua esse id. Pariatur dolor et adipisicing ea mollit.
          Ipsum non irure proident ipsum dolore aliquip adipisicing laborum
          irure dolor nostrud occaecat exercitation. Culpa qui reprehenderit
          nostrud aliqua reprehenderit et ullamco proident nisi commodo non ut.
          Ipsum quis irure nisi sint do qui velit nisi. Sunt voluptate eu
          reprehenderit tempor consequat eiusmod Lorem irure velit duis Lorem
          laboris ipsum cupidatat. Pariatur excepteur tempor veniam cillum et
          nulla ipsum veniam ad ipsum ad aute. Est officia duis pariatur ad
          eiusmod id voluptate. Duis est est do dolore magna proident labore do
          irure. Irure aliquip cillum est esse mollit laborum esse anim sint ut
          laboris ut proident culpa. Exercitation duis eu officia commodo.
          Proident commodo esse occaecat velit enim non dolor ad nostrud.
          Adipisicing enim est cupidatat culpa pariatur aliqua id tempor ipsum.
          Ad nulla enim dolore ullamco occaecat mollit non veniam voluptate
          labore. Culpa aute sit exercitation ad anim ad duis ut consequat id
          irure. Ullamco id adipisicing sint proident anim eu eiusmod. Eu nisi
          magna elit cupidatat nostrud duis anim proident enim nulla. Eu eu
          veniam aute id aute. Tempor dolore dolor aute anim labore deserunt
          anim ea est est. Id est amet minim est dolor et eu excepteur. Officia
          sunt deserunt et proident ut esse aliqua ullamco nulla minim anim
          adipisicing amet sit. Commodo ea Lorem excepteur dolor quis mollit
          mollit veniam cillum minim quis ut ex. Est pariatur laborum
          consectetur sit ut anim voluptate est reprehenderit tempor. Pariatur
          aliquip culpa esse ipsum aute reprehenderit id commodo ea duis fugiat
          voluptate nostrud excepteur. Cupidatat nostrud consequat id esse non
          sunt ullamco eiusmod cillum laboris. Dolore consequat sit do cillum
          mollit labore. Ut consequat dolore et ea elit. Do ea enim irure eu
          velit aute. Irure esse aliqua ad velit culpa laboris sit nisi Lorem
          dolore nostrud. Exercitation dolore minim anim do aliqua quis
          reprehenderit do enim consectetur. Minim officia commodo amet sint
          commodo ea officia. Lorem qui dolore occaecat elit irure aliquip sunt
          tempor consectetur laborum adipisicing sunt. Laboris nisi culpa
          cupidatat fugiat ullamco deserunt mollit incididunt ipsum. Commodo
          nostrud qui esse mollit sunt velit est dolor esse irure pariatur velit
          elit cillum. Amet eiusmod culpa aute mollit Lorem do laboris sit sunt
          exercitation. Duis sunt ea ut consectetur cillum ullamco esse culpa.
          Non Lorem non incididunt labore velit cillum nulla est consequat
          nostrud ea anim.
        </p>
styles.js
.containerInner {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: #fff;
  width: 462px;
  border: 1px solid black;
  border-radius: 10px;
+  height: 448px;
}
.{
.contents {
  padding: 8px 24px;
+  overscroll-behavior: contain;
+  overflow-y: scroll;
}
}

以上で簡単なモーダルが完成しました!
最後に一手間加えましょう

createPortalを使ってモーダルを表示する

cratePortalを利用することで任意のDOM要素に子要素を追加できます。
今回はbody要素の最後にモーダルコンポーネントを追加して要素の上に常にモーダルを表示できるようにします。
1.Componetnts/reactPortal.jsxファイルを作成

2.createPortalの実装

reactPortal.jsx
import { createPortal } from "react-dom";

export const ModalPortal = ({ children }) => {
  return createPortal(children, document.body);
};
app.js
  return (
    <div className="App">
      <button onClick={onOpen} type="button" className="button openButton">
        Open Modal
      </button>
+      {isOpen && (
+        <ModalPortal>
+          <Modal onClose={onClose} />
+        </ModalPortal>
+      )}
    </div>
  );

3.完成

Discussion