
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、Laravel & React
を使った開発も少し慣れてきた今日この頃です。
そして、これは皆さん同じだと思うのですが、何でも慣れてきたら「できるだけ楽して同じ効果を出したい」と思うんじゃないでしょうか。
つまり、コンポーネントや共通コードをつくって「使いまわし」ができるようにする「ショートカット」ですね。
ただ、この間このコンポーネントさえも「何回も使ってめんどう…」という場面に遭遇しました。
それが…
独自の confirm() モーダル
です。
↓↓↓ こういうやつですね。
というのも、基本的なショートカットとしてはこのようなモーダルを「コンポーネント化」するわけですが、同じページで複数必要な場合、何度も似たコード書かないといけなくなりメンドウになってしまいます(useState
で{表示/非表示}部分をすべてのモーダルにつくるのは となります)
そこでなんとかJavaScript
に標準搭載されているconfirm()
のように使えないだろうかと考えた結果が今回の記事になります。
↓↓↓ こんな感じです。
if(confirm('よろしいですか?')) {
// ここで OK された時の処理
}
ぜひ何かの参考になりましたら嬉しいです。
「クラフトビールの価値を
普通のビールと同じに
考えちゃダメゼッタイ
」
目次 [非表示]
やりたいこと
冒頭でも書きましたが、JavaScript
に標準搭載されているconfirm()
「のような使い方」ができるようにしていきます。
ただ、結果としてはif()
での分岐はブラウザ側をいじらないといけないと思いましたので、最終的には以下のように実装しました。
modalRef.current.show(message)
.then(() => {
console.log('OK のとき')
})
.catch(() => {
console.log('キャンセルのとき')
});
では、その研究結果をご報告します
独自 confirm() モーダル・コンポーネントをつくる
いきなりですが、confirm()
の代わりとなるコンポーネントConfirmationModal
をつくっていきます。
resources/js/Components/ConfirmationModal.jsx
import {forwardRef, useImperativeHandle, useRef, useState} from "react";
export default forwardRef((props, ref) => {
// Data
const [showModal, setShowModal] = useState(false);
const [message, setMessage] = useState('');
// from Parent
const callbacksRef = useRef({});
useImperativeHandle(ref, () => ({
show(message) {
setShowModal(true);
setMessage(message);
return new Promise((resolve, reject) => {
callbacksRef.current = {resolve, reject}; // ここで Promise の結果実行関数を保存しておく
});
},
}));
// Click
const handleOkClick = () => {
setShowModal(false);
callbacksRef.current.resolve();
};
const handleCancelClick = () => {
setShowModal(false);
callbacksRef.current.reject();
};
return (
<>
{showModal && (
<div className="relative z-10">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div className="fixed inset-0 z-10">
<div className="flex min-h-full items-end justify-center p-4 text-center items-center">
<div className="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all w-96">
<div className="bg-white p-4">
<div className="flex items-start">
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100">
<svg className="h-6 w-6 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 10.5v3.75m-9.303 3.376C1.83 19.126 2.914 21 4.645 21h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 4.88c-.866-1.501-3.032-1.501-3.898 0L2.697 17.626zM12 17.25h.007v.008H12v-.008z" />
</svg>
</div>
<div className="mt-3 mt-0 ml-4 w-full">
{message}
</div>
</div>
</div>
<div className="bg-gray-200 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button
type="button"
className="rounded-md border border-gray-300 bg-blue-600 px-4 py-2 text-base font-medium text-white"
onClick={handleOkClick}>
OK
</button>
<button
type="button"
className="rounded-md border border-gray-300 bg-gray-50 px-4 py-2 text-base font-medium text-gray-700 mr-2"
onClick={handleCancelClick}>
キャンセル
</button>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
});
今回このコンポーネントを作るときに初めてReact
のforwardRef
、 useImperativeHandle
を使うことになりました。
これらを使うと、なんと「コンポーネント内にあるメソッドを(外側から)実行できるようになる」んですね。
つまり、以下の例でいうとYourComponent
の中に定義されているhello()
をメインページから実行することができるようになります。
【メインページ】
import ConfirmationModal from "@/Components/YourComponent";
// 省略
// ここで YourComponent の hello() が実行できる
return (
<>
<YourComponent></YourComponent>
</>
);
【コンポーネント】
// 省略
useImperativeHandle(ref, () => ({
hello() {
// メインページから実行できる
},
}));
// 省略
こうすることで、何度も{表示/非表示}切り替えをメインページでしなくてよくなります。
そして、if()
分岐の代わりに使ったのがPromise
です。
Promise
は、めちゃくちゃ簡単に言うと「タイミングをずらして分岐できる」機能です。
今回のケースで言うと、「OK もしくは、キャンセルが決定されるのは使っているユーザーのタイミング」になるので、モーダルが表示されたときにresolve
とreject
を保持しておいて、実際の選択時に最終決定できるようにしています。
これで、以下のような実装ができるようになりました。(axios
っぽくしたかったんですね)
modalRef.current.show(message)
.then(() => {
// OK のとき
})
.catch(() => {
// キャンセルのとき
});
独自コンポーネントを使う
では、先ほどつくったConfirmationModal
を使ってみましょう。
今回は同じページで3つの確認モーダルが表示できるようにしますが、使っているコンポーネントはたった1つだけという点に注目してください。
resources/js/Pages/ConfirmationModal/Index.jsx
import ConfirmationModal from "@/Components/ConfirmationModal";
import {useRef} from "react";
export default function Index() {
// Modal
const modalRef = useRef();
const handleOpenModalOne = () => {
const message = 'よろしいですか?(1番目のボタン)';
modalRef.current.show(message)
.then(() => console.log('OK がクリックされました(1番目のボタン)'))
.catch(() => console.log('キャンセルがクリックされました(1番目のボタン)'));
};
const handleOpenModalTwo = () => {
const message = 'よろしいですか?(2番目のボタン)';
modalRef.current.show(message)
.then(() => console.log('OK がクリックされました(2番目のボタン)'))
.catch(() => console.log('キャンセルがクリックされました(2番目のボタン)'));
};
const handleOpenModalThree = () => {
const message = 'よろしいですか?(3番目のボタン)';
modalRef.current.show(message)
.then(() => console.log('OK がクリックされました(3番目のボタン)'))
.catch(() => console.log('キャンセルがクリックされました(3番目のボタン)'));
};
return (
<div className="p-5">
<button
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
onClick={handleOpenModalOne}>
モーダルを表示する(1番目のボタン)
</button>
<br />
<button
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
onClick={handleOpenModalTwo}>
モーダルを表示する(2番目のボタン)
</button>
<br />
<button
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
onClick={handleOpenModalThree}>
モーダルを表示する(3番目のボタン)
</button>
{/* モーダル */}
<ConfirmationModal ref={modalRef}></ConfirmationModal>
</div>
);
}
ConfirmationModal
の使い方はシンプルです。
まず、useRef()
から作ったmodalRef
を用意し、以下のようにref
へセットするだけで完了です。
const modalRef = useRef();
// 省略
<ConfirmationModal ref={modalRef}></ConfirmationModal>
あとは、確認モーダルを表示したいときに、show()
を呼び出し、axios
のようにドットでつないでthen
or catch
で分岐をします。
ルートをつくる
では、先ほどのページがLaravel
で実行できるようにルートを追加しておいてください。
routes/web.php
// 省略
Route::get('confirmation_modal', fn() => Inertia::render('ConfirmationModal/Index'));
テストしてみる
では実際にテストしてみましょう
まずはVite
を起動して、「http://******/confirmation_modal」へアクセスしてください。
すると・・・・・・
はい
ボタンが表示されました。
では、まずは「1番目のボタン」をクリックしてみましょう。
どうなるでしょうか・・・・・・
はい
モーダルが表示されました。
では、ブラウザのコンソールを開いて、まずは「OK」ボタンをクリックしてみましょう。
うまくいくでしょうか・・・・・・
はい
コンソールにうまく表示がでました。
では、次に「2番目のボタンをクリックし、キャンセルを選択」してみましょう。
すると・・・・・・
はい
こちらもうまく表示が出ました。
すべて成功です
デモページを用意しました
せっかくなのでデモページを用意しました。
ぜひ実際に確かめてみてください。
企業様へのご提案
今回のように特によく利用する機能は、通常のコンポーネント化ではない方法を利用することでより省コード化することができ作業効率がアップします。
そういった開発をご希望でしたら、ぜひお問い合わせからご連絡ください。
お待ちしております。
おわりに
ということで、今回はconfirm()
を代替する独自モーダルをつくってみました。
今回はconfirm()
でしたが、alert()
でも同じですので、今回のコードを改造してご自身で作ってみてくださいね。
ではでは〜
「グルメな友人御用達
のお寿司屋さんウマすぎでした
(しかもお値ごろ )」