まくろぐ

React でモーダルダイアログを表示する (react-modal)

更新:
作成:

react-modal パッケージが提供する ReactModal コンポーネントを使用すると、React アプリ内で簡単にモーダルダイアログを実現することができます。

/p/q6fnu29/img-001.png

上記は、設定 ボタンを押してモーダルな設定ダイアログを開いたときの表示例です。

react-modal のインストール

ReactModal コンポーネントを使うために、react-modal パッケージと TypeScript の型定義ファイルをインストールします。

$ npm install --save react-modal
$ npm install --save-dev @types/react-modal

ReactModal コンポーネントを使用する

ReactModal の使い方

ReactModal コンポーネントは、isOpen プロパティでダイアログの表示・非表示を制御するようになっています。

// import ReactModal from 'react-modal';

<ReactModal
  isOpen={this.props.isOpen}
  onAfterOpen={this.handleOpen}
  onRequestClose={this.handleClose}
  style={this.customStyles}
  contentLabel="Settings"
>
  // ... フォームの内容などをここに記述 ...
</ReactModal>

onAfterOpen でオープン時、onRequestClose でクローズ時のイベントをハンドルすることができます。 ReactModal は自分自身のダイアログを自動で閉じたりしないので、onRequestClose に設定したハンドラ内で、isOpen プロパティに渡す値を false に設定してダイアログを閉じる必要があります。 onRequestClose ハンドラは、Esc キーを押したときや、ダイアログ外の領域をクリックしたときなどに呼び出されます。

コンポーネントの作成

下記のサンプルコードは ReactModal を使ったコンポーネントの作成例です。 ここでは、ユーザー名を入力可能な設定ダイアログを想定しています。 React を使ったフォームの作成方法などは次の記事を参照してください。

components/settingsDialog.tsx
import * as React from 'react';
import ReactModal from 'react-modal';

interface Props {
  /** このダイアログを表示するなら true */
  isOpen: boolean;
  /** このダイアログを閉じるときのコールバック */
  onClose?: () => void;
}

interface State {
  username: string;
}

export class SettingsDialog extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      username: ''
    };

    // 具体的に #root 要素などを指定した方がよい?
    ReactModal.setAppElement('body');
  }

  public render(): React.ReactNode {
    return <div>
      <ReactModal
        isOpen={this.props.isOpen}
        onAfterOpen={this.handleOpen}
        onRequestClose={this.handleClose}
        style={this.customStyles}
        contentLabel="Settings"
      >
        <form onSubmit={this.handleSubmit}>
          <label>ユーザー名
            <input type="text" autoFocus value={this.state.username}
              onChange={this.handleChangeUsername}></input>
          </label>
        </form>
      </ReactModal>
    </div>;
  }

  // フォームのサブミット時にダイアログを閉じる
  private handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    this.handleClose();
  }

  private handleChangeUsername = (event: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({username: event.target.value})
  }

  // ダイアログが開いたときに呼び出される
  private handleOpen = () => {
    // ここで設定情報などを読み込む
  }

  // ダイアログ領域外のクリックや、ESCキーを押したときに呼び出される
  private handleClose = () => {
    // 親コンポーネントにダイアログを閉じてもらうためのコールバック通知
    this.props.onClose?.();
  }

  // スタイルのカスタマイズ
  private customStyles: ReactModal.Styles = {
    // ダイアログ内のスタイル(中央に表示)
    content: {
      top: '50%',
      left: '50%',
      right: 'auto',
      bottom: 'auto',
      marginRight: '-50%',
      transform: 'translate(-50%, -50%)'
    },
    // 親ウィンドウのスタイル(ちょっと暗くする)
    overlay: {
      background: 'rgba(0, 0, 0, 0.2)'
    }
  }
}

作成したコンポーネントを使用する

上記で作成した SettingsDialog コンポーネントの使用例です。 設定 ボタンを押したときに、SettingsDialogisOpen プロパティを true に設定することでダイアログを表示しています。

components/app.tsx
import * as React from 'react';
import {SettingsDialog} from './settingsDialog';

interface State {
  isDialogOpen: boolean;
}

export class App extends React.Component<{}, State> {
  constructor(props: {}) {
    super(props);
    this.state = {
      isDialogOpen: false,
    };
  }

  public render(): React.ReactNode {
    return <div>
      <button onClick={this.openDialog}>設定</button>
      <SettingsDialog isOpen={this.state.isDialogOpen} onClose={this.closeDialog} />
    </div>;
  }

  // ダイアログを開く
  private openDialog = () => {
    this.setState({isDialogOpen: true});
  }

  // ダイアログからのコールバックでダイアログを閉じてあげる
  private closeDialog = () => {
    this.setState({isDialogOpen: false});
  }
}

設定値の保存と読み出し

ReactModal コンポーネントの onAfterOpenonRequestClose プロパティを設定しておくと、ダイアログを開いたときと閉じたときに任意の処理を行うことができます。

<ReactModal
  isOpen={this.props.isOpen}
  onAfterOpen={this.handleOpen}
  onRequestClose={this.handleClose}
  ...>

設定ダイアログのようなものを作るのであれば、このタイミングでフォームに入力した設定情報を保存・復旧させるとよいでしょう。 次の例では、Web ブラウザの localStorage に設定情報を保存しています。

private handleOpen = () => {
  // 設定情報を読み込む
  const name = localStorage.getItem('username') || '';
  this.setState({username: name});
}

private handleClose = () => {
  // 設定情報を保存
  localStorage.setItem('username', this.state.username);

  // 親コンポーネントにダイアログを閉じてもらうためのコールバック通知
  this.props.onClose?.();
}

ここでは、簡易的な説明のため localStorage を使用しましたが、localStorage はセキュアではないとされているので、ユーザーの秘密情報をそのまま保存するのは避けてください。 他の保存手段としては、Web アプリであればサーバーサイドセッションを使う方法、Electron アプリであればローカルファイルに保存する方法(electron-store など)があります。

関連記事

まくろぐ
サイトマップまくへのメッセージ