九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、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()
でも同じですので、今回のコードを改造してご自身で作ってみてくださいね。
ではでは〜❗
「グルメな友人御用達
のお寿司屋さんウマすぎでした❗
(しかもお値ごろ 😄✨)」