はじめに
React hooks を使ったローカル state でモーダルをコンポーネントにしてみました。react-modal がそもそもお手軽なのですが、コンポーネント化しておくと、まあ、それなりには汎用に使えるかと。
親コンテナ → 子コンポーネント(とオーバレイするモーダルコンポーネント)という使い方を想定しています。
材料(一人分)
npm install -S react-modal @material-ui/icons
もちろん React アプリであるのが前提です。モーダルのアイコンに @material-ui/icons 使っていますが、気に食わなければいくらでも置き換えて頂ければと。
実装例では Typescript で書いてはいますが、ほぼ依存してないので、Typescript 記法でなくてもかまいません。
モーダルの実装
//
// 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 なりを使って表示すべきです。