React.jsでModalcomponentの実装


Implementation-of-the-Modal-components-in-React.js

こんにちは、現在ShouldBeeではReact.jsを用いたUIの開発をしています。

ReactにはModalを実装した公開コンポーネントがいくつかあるのですが、React betaに対応していなかったのとアニメーション等の細かい設定を自分で設定したかったので自前で実装しました、今回はその紹介をしたいと思います!

コンポーネント本体は下記のようになります。

親コンポーネントのstateをisActiveとして受け取り表示、非表示を切り替えています。
またモーダルを閉じるとき親コンポーネントのstateを更新する必要があるので、親コンポーネントのstate更新関数(onClose)を受け取っています。

アニメーションはAnimate.cssを使用しています。

import React from "react";
import classNames from "classnames";

export default class Modal extends React.Component {
  static propTypes = {
    onClose: React.PropTypes.func.isRequired,
    isActive: React.PropTypes.bool.isRequired
  }

  constructor(props) {
    super(props);
    this.state = {isActive: false, willClose: false};
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.isActive) {
      this.setState({isActive: true, willClose: false})
    } else {
      const self = this;
      this.setState({willClose: true});
      setTimeout(() => {
        self.setState({isActive: false});
      }, 500); // wait to animate the modal closing.
    }
  }

  onClose() {
    this.props.onClose();
  }

  render() {
    const modalClasses = classNames({
      "animated": true,
      "slideInDown": this.state.isActive,
      "slideOutUp": this.state.willClose
    });

    if (this.state.isActive) {
      return (
        <div>
          <div
            style={{
              display: "block",
              zIndex: "8887",
              position: "absolute",
              top: "0",
              bottom: "0",
              left: "0",
              right: "0",
              background: "gray",
              opacity: "0.7"
            }}
            onClick={::this.onClose}
          >
          </div>
          <div
            className={modalClasses}
            style={{
              display: "block",
              zIndex: "8888",
              height: "200px",
              width: "300px",
              position: "absolute",
              top: "0",
              bottom: "0",
              left: "0",
              right: "0",
              margin: "auto",
              background: "white"
            }}
          >
            <h1>Modal!!</h1>
            <button className="btn" onClick={::this.onClose}>
              Close
            </button>
          </div>
        </div>
      );
    } else {
      return false;
    }
  }
}

子コンポーネントが親コンポーネントのstateを直接更新するのはあまり好ましくありませんが、ビジネスロジックに直接関わるstateではないので良しとしています。

このコンポーネントは下記の様に使用できます。

import React from "react";
import Modal from "components/Modal";

export default class TopPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isModalActive: false};
  }

  openModal() {
    this.setState({isModalActive: true});
  }

  closeModal() {
    this.setState({isModalActive: false});
  }

  render() {
    return (
      <div>
        <Modal isActive={this.state.isModalActive} onClose={::this.closeModal}/>
        <button className="btn" onClick={::this.openModal}>
          Open modal
        </button>
      </div>
    );
  }
}

{::this...}の書き方についてはこちらの記事を参考にしてください。

Gistはこちらからどうぞ。