React Hooksを使ったモーダル実装

React HooksのuseState・useRefを使ったモーダル実装についてまとめました。

今回実装するのは、以下のようなモーダルになります。

![ezgif.com-video-to-gif (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584128/edc5f6bf-5412-b8a1-14af-ba31d808382a.gif)

#完成コード
##React

```react

import React, {useState, useRef} from 'react';

const App = () => {
  const [modal, setModal] = useState(false)
  const modalRef = useRef()

  return (
    <div>
      <button onClick={() => setModal(true)}>Open</button>
      <div className={`modal__overlay ${modal && "is-opened"}`}  onClick={e=>{if(modalRef.current === e.target) setModal(false)}} ref={modalRef}>
        <div className="modal__box">
          <button className="modal__closeBtn" onClick={() => setModal(false)}>×</button>
          <div>モーダルテキスト</div>
        </div>
      </div>
    </div>
  );
}

export default App;

```

##CSS

```css
.modal__overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,0.4);
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity .3s, visibility .3s;
}
.is-opened{
  visibility: visible;
  opacity: 1;
}
.modal__box{
  background-color: #fff;
  position: relative;
  padding: 30px 20px;
}
.modal__closeBtn{
  position: absolute;
  top: 0;
  right: 0;
}

```


#実装流れ
##1.モーダルの見た目作成

まず、オーバーレイ要素（画面全体を覆う薄暗い要素）とモーダルボックスを作成します。

```react

import React from 'react';

const App = () => {
  return (
    <div>
      {/* オーバーレイ要素でモーダルボックスを囲む */}
      <div className="modal__overlay">
        <div className="modal__box">
          <div>モーダルテキスト</div>
        </div>
      </div>
    </div>
  );
}

export default App;

```

```css
.modal__overlay{
  /* 画面全体を覆う設定 */
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,0.4);
  z-index: 999;
  /* 画面中央にモーダルを表示させる設定 */
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal__box{
  background-color: #fff;
  padding: 30px 20px;
}
```

ここまでの実装画面
![スクリーンショット 2020-06-25 11.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584128/b64d52c5-b389-2bbe-3042-2c26a89994ab.png)

##2.モーダルの表示切り替えの実装
次に、ボタンクリックでモーダルの表示・非表示を切り替えます。

```react
// useState追加
import React, {useState} from 'react'; 

const App = () => {
  // モーダルの表示・非表示をstate管理(true：表示 false：非表示)
  // ※modal:現在のstate値 setModal:state値を更新するための関数 false：初期値
  const [modal, setModal] = useState(false)

  return (
    <div>
      {/* ボタンクリック後、modalをtrueに変更 */}
      <button onClick={() => setModal(true)}>Open</button>
      {/* modalがtrueの場合、is-openedクラスを付与 */}
      <div className={`modal__overlay ${modal && "is-opened"}`}>
        <div className="modal__box">
          {/* ボタンクリック後、modalをfalseに変更 */}
          <button className="modal__closeBtn" onClick={() => setModal(false)}>×</button>
          <div>モーダルテキスト</div>
        </div>
      </div>
    </div>
  );
}
```

```css
.modal__overlay{
  z-index: 999;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  /* 初期状態は非表示 */
  opacity: 0;
  visibility: hidden;
  /* ゆっくり表示させる */
  transition: opacity .3s, visibility .3s;
}
/* モーダル表示(modalがtrueになったら付与される) */
.is-opened{
  visibility: visible;
  opacity: 1;
}
.modal__box{
  background-color: #fff;
  padding: 30px 20px;
  position: relative;
}
.modal__closeBtn{
  position: absolute;
  top: 0;
  right: 0;
}
```

ここまでの実装画面
![ezgif.com-video-to-gif (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584128/3526df5d-d596-44a4-ae9c-cd8171da4418.gif)



##3.オーバーレイクリックでモーダルを閉じる処理
最後に、オーバーレイをクリックした時にもモーダルを閉じるようにしたいと思います。

   

```react
<div className={`modal__overlay ${modal && "is-opened"}`}  onClick={() => setModal(false)}>
```
このように追記することで、オーバーレイクリック後にモーダルを閉じることができますが、これだとモーダルボックス内をクリックした時にも閉じてしまいます。
![ezgif.com-video-to-gif (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584128/fc6f80f3-4fe7-0c24-8a0f-27cc89d3f5d6.gif)
そこで、コンポーネントのDOMノードに直接アクセスできるようになるuseRefフックを使用します。


```react
// useRef追加
import React, {useState, useRef} from 'react';

const App = () => {
  // modalRefの定義 ※modalRef.currentでオーバーレイ要素を取得可能に
  const modalRef = useRef()
  const [modal, setModal] = useState(false)

  return (
    <div>
      <button onClick={() => setModal(true)}>Open</button>
      {/* クリックした要素がmodalRef.currentと一致した場合、modalをfalseに変更 */}
      <div className={`modal__overlay ${modal && "is-opened"}`} onClick={e=>{if(modalRef.current === e.target) setModal(false)}} ref={modalRef}>
        <div className="modal__box">
          <button className="modal__closeBtn" onClick={() => setModal(false)}>×</button>
          <div>モーダルテキスト</div>
        </div>
      </div>
    </div>
  );
}

export default App;
```

#完成
![ezgif.com-video-to-gif (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584128/edc5f6bf-5412-b8a1-14af-ba31d808382a.gif)

このようにuseState・useRefを使用することで簡単にモーダル実装ができますので、是非お試し下さい！
#参考文献
https://chaika.hatenablog.com/entry/2019/12/08/090000
https://ichiki.netlify.app/blog/20191218_react_click_out/