Help us understand the problem. What is going on with this article?

React 汎用モーダル - React hooks + react-modal で比較的お手軽に

はじめに

React hooks を使ったローカル state でモーダルをコンポーネントにしてみました。react-modal がそもそもお手軽なのですが、コンポーネント化しておくと、まあ、それなりには汎用に使えるかと。

親コンテナ → 子コンポーネント(とオーバレイするモーダルコンポーネント)という使い方を想定しています。

材料(一人分)

npm install -S react-modal @material-ui/icons

もちろん React アプリであるのが前提です。モーダルのアイコンに @material-ui/icons 使っていますが、気に食わなければいくらでも置き換えて頂ければと。

実装例では Typescript で書いてはいますが、ほぼ依存してないので、Typescript 記法でなくてもかまいません。

モーダルの実装

Modal.tsx
//
//  Components/Modal - モーダル画面
//
import  *       as React                from 'react';
import  Modal                           from 'react-modal';
import  IconButton                      from '@material-ui/core/IconButton';
import  InfoCircle                      from '@material-ui/icons/InfoRounded';
import  CheckCircle                     from '@material-ui/icons/CheckCircle';
import  CancelRounded                   from '@material-ui/icons/CancelRounded';

// クリックイベントの定義
type            ClickEventHandlerType   =  ( ( ( e : React.MouseEvent ) => void ) | undefined )

// export Props - このコンポーネントのインターフェイスの定義
export interface        Props {

        app?            : string                        // モーダル表示すべきエレメントの ID。デフォルトは 'root'
        title?          : string                        // モーダル画面のタイトル
        text?           : string                        // モーダル画面の詳細テキスト
        onCancel?       : ClickEventHandlerType         // キャンセル時にハンドルされる関数
        onConfirm?      : ClickEventHandlerType         // 完了時にハンドルされる関数
}

//  export default - アラート表示用モーダル画面のコンポーネント
export default          React.memo( function ModalWindow( props : Props ) {

    // Props を展開する
    const               {
        app                     = 'root',
        title                   = '確認してください',
        text                    = undefined,
        onConfirm               = undefined,
        onCancel                = undefined
    }                   = props;

    // 指定されたエレメントを得る
    const           element         = document.getElementById( app );
    const           root            = element ? element : undefined;

    // モーダルが開いた時の処理
    const           afterOpen       = () => {
    };

    // 完了ボタンの定義
    const           confirmButton   = ( handler :  ClickEventHandlerType ) => {

        return (
            handler ?
                <IconButton onClick = { handler }>
                    <CheckCircle color = 'secondary' fontSize = 'large' />
                </IconButton>
            : null
        );
    };

    // キャンセルボタンの定義
    const           cancelButton    = ( handler : ClickEventHandlerType ) => {

        return(
            handler ?
                <IconButton onClick = { handler }>
                    <CancelRounded color = 'primary' fontSize = 'large' />
                </IconButton>
            : null
        );
    };

    // モーダル画面のスタイル
    const           styles          = {
        overlay         : {
            position            : 'fixed',
            top                 : 0,
            right               : 0,
            bottom              : 0,
            left                : 0,
            backgroundColor    : 'rgba( 0, 0, 0, .4 )',
            zIndex              : 10000,
        },
        content         : {
            position            : 'absolute',
            display             : 'block',
            textAlign           : 'center',
            backgroundColor     : 'whitesmoke',
            top                 : '50%',
            left                : '50%',
            right               : 'auto',
            bottom              : 'auto',
            width               : '50%',
            border              : '2px solid gray',
            transform           : 'translate( -50%, -50% )',
            outline             : 'transparent',
        }
    };

    // モーダル画面をレンダリングする
    return (

        <Modal
            appElement          = { root }
            isOpen              = { !! text }
            onRequestClose      = { onCancel }
            onAfterOpen         = { afterOpen }
            style               = { styles }
        >

            { /* モーダル画面の詳細を表示する */ }
            <h4> <InfoCircle color = 'secondary' fontSize = 'large' /> </h4>
            <h4> { title } </h4>
            <div> { text } </div>

            <br />

            { /* ボタンを表示する */ }
            { confirmButton( onConfirm ) }
            { cancelButton( onCancel ) }

        </Modal>
    )
} );

使い方

親コンテナ側

このモーダルは、親コンテナのローカル state で開閉を管理しています。

親コンテナで、子コンポーネント+モーダルに渡る表示文字列をローカル state として管理します。この text はモーダルのテキストになります。テキストがあればモーダルが開きます。ローカル state の初期値は undefined なのでモーダルは閉じています

const           [ text, openModal ]         = useState( undefined );

親コンテナでモーダルが閉じられた時のハンドラ onConfirm, onCancel を定義します。

子コンポーネント側

以下のように、モーダル画面を子コンポーネント内に定義し、モーダルとともにコンテンツをレンダリングしてください。

import Modal from 'Modal';

...

return(
  <div>
    { /* 子コンポーネントのレンダリング内容 */ }
    <Content>
    ...
    </Content>

    { /* モーダル画面 */ }
    <Modal text = { text } app = { app } title = { title } onCancel = { onCancel } onConfirm = { onConfirm } />
  </div>
);

title : モーダルに出すタイトルテキスト - 省略時は「確認してください」
text : モーダルに出すテキスト - undefined なら、モーダルを閉じる
app : モーダルを出すエレメントの ID - 省略時は 'root' (全画面)

親コンテナ側に戻って

モーダルを出す時は以下のようにします。

openModal( 'モーダルに出すテキスト' );

モーダルを閉じる時、ダイアログ等なら、閉じるボタンのコールバック側 onClose, onConfirm などで行うのが通常かと思いますが、以下のようにローカル state の text を undefined にすると、モーダルが閉じますので、親コンポーネントで任意のタイミングで行ってください。

const           onCancel        = () => {
    openModal( undefined );
};

モーダルコンポーネント内にスタイルも定義できますので、アラートや確認ダイアログだけでなく、「処理中表示」「詳細画面」など、いろいろ改造できるかと思います。

注意

モーダルが開いても、その場でモーダルの処理は終了し、その他の処理は動作していることに留意してください。 同期が必要なら呼び出しを Promise なりを使って表示すべきです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away