React入門その⑥ react-modal

概要

Webで alert による次のようなそっけないモーダルダイアログボックスでなくて、もう少し見栄えのよいものにしたいような場合、自分で作らなくても既に用意されているものがあるのでそれを使おうという話です。

alertによるモーダルダイアログボックス

これを次のようにしてみました。このメッセージ以外の部分の見栄えの変更の中でやっております。

react-modalによるモーダルダイアログボックス

結論としては、随分手間がかかりました。

なお、モーダルダイアログボックスだけでなく、いろいろなものが利用可能(例えば bit.dev 参照)です。

インストール

  • 以下はあらかじめインストールされていました。
    • react // バージョン 16.12.0
    • react-dom // バージョン 16.12.0
    • babel-cli // バージョン 6.24.1
    • babel-core // バージョン 6.25.0
    • babel-loader // バージョン 7.1.1
    • babel-plugin-transform-class-properties // バージョン 6.24.1
    • babel-preset-env // バージョン 1.7.0
    • babel-preset-react // バージョン 6.24.1
    • webpack // バージョン 4.41.5
    • webpack-cli // バージョン 3.3.10
    • webpack-dev-server // バージョン 3.10.1
  • 以下をインストールします。
> yarn add react-modal style-loader css-loader

変更前コード

:
:
export default class IndecisionApp extends React.Component {
  state = {
    options: []
  };
:
:
 handlePick = () => {
    const randomNum = Math.floor(Math.random() * this.state.options.length);
    const option = `選ばれた選択肢は ${this.state.options[randomNum]}`;
    alert(option);
  } 
:
:

変更後コード

上記コードに対し、alert を記述した1行が変更され、OptionModal のインポートと state を追加しました。

:
:
import OptionModal from './OptionModal'; // モーダルダイアログボックス

export default class IndecisionApp extends React.Component {
  state = {
    options: [],
    selectedOption: null// 決定された選択肢
  }; 
:
:
   handlePick = () => {
    const randomNum = Math.floor(Math.random() * this.state.options.length);
    const option = this.state.options[randomNum];
    this.setState(() => ({ selectedOption: option })); // alertの代わりにここを変更
  } 
:
:

あと、 OptionModal  を描画するために本クラスの render() に以下を追加しました。

<OptionModal
  selectedOption={this.state.selectedOption}
  handleClearSelectedOption={this.handleClearSelectedOption}
/> 

ここでは[了解]ボタンをクリックしたときにモーダルダイアログボックスを閉じるための処理をおこなうため、新たに追加した handleClearSelectedOption を渡しています。これの処理は以下です。

handleClearSelectedOption = () => {
  this.setState(() => ({ selectedOption: null}));
} 

次のように記載するより簡略された記法となっています。

 handleClearSelectedOption = () => {
  this.setState(() => {
    return ({
       selectedOption: null
    });
  });
}  

追加したOptionModal.js は基本は次です。

import React from 'react';
import Modal from 'react-modal';

Modal.setAppElement("#app"); // これがないと警告が出る 
 
const OptionModal = (props) => (
  <Modal
    isOpen={!!props.selectedOption}
    contentLabel="選ばれた選択肢"
  >
    <h3 >選ばれた選択肢</h3>
    {props.selectedOption && <p >{props.selectedOption}</p>}
    <button  onClick={props.handleClearSelectedOption}>了解</button>
  </Modal>
); 

export default OptionModal; 

react-modal のバージョンが2.2.2とかだと問題がないのですが、本バージョンだと、上記のsetAppElement のコードがないとChromeDevToolsで下記のような警告が見えてうっとおしいです。詳細はこちら参照。

warning.js?d96e:34 Warning: react-modal: App element is not defined. Please use Modal.setAppElement(el) or set appElement={el}. This is needed so screen readers don't see main content when modal is opened. It is not recommended, but you can opt-out by setting ariaHideApp={false}.

ただし、これだと、次のようなひどい見た目になってしまいます。

react-modal

最終的には次のようなコードにしました。

import React from 'react';
import Modal from 'react-modal';

Modal.setAppElement("#app"); // これがないと警告が出る 
const OptionModal = (props) => (
  <Modal
    isOpen={!!props.selectedOption}
    onRequestClose={props.handleClearSelectedOption} // ESCキーやダイアログの外のクリック時
    contentLabel="選ばれた選択肢"
    closeTimeoutMS={200}
    className="modal"
  >
    <h3 className="modal__title">選ばれた選択肢</h3>
    {props.selectedOption && <p className="modal__body">{props.selectedOption}</p>}
    <button className="button" onClick={props.handleClearSelectedOption}>了解</button>
  </Modal>
);

export default OptionModal; 

そして、styles/components/_modal.scss というファイルを作り、styles/styles.scss というファイルから、その他のscssファイルと共に次のようにインポートしました。

 @import './components/widget'; 

_modal.scss の内容は次のようにしました。結構複雑ですね。

.ReactModalPortal > div {
  opacity: 0;
}

.ReactModalPortal .ReactModal__Overlay {
  align-items: center;
  display: flex;
  justify-content: center;
  transition: opacity 200ms ease-in-out;

  &--after-open {
    opacity: 1;
  }
  
  &--before-close {
    opacity: 0;
  }
}

.modal {
  background: $light-blue;
  color: white;
  max-width: 30rem;
  outline: none;
  padding: $l-size;
  text-align: center;

  &__title {
    margin: 0 0 $m-size 0;
  }
  
  &__body {
    font-size: 2rem;
    font-weight: 300;
    margin: 0 0 $l-size 0;
    word-break: break-all;
  }
} 

ここで以下のようなクラス名がでてきます。

  • ReactModalPortal
  • ReactModal__Overlay
  • ReactModal__Overlay–after-open
  • ReactModal__Overlay–before-close

これはどうやって調べたかというと、ChromeDevToolsElementsビューで調べました。