はじめに
今回はReacr,React-Routerを使用し以下のような要件のモーダルを実装していきます。
- モーダルにURLを付与したい。(モーダルを表示している状態を別ページ扱いとしたい)
- 別のタブでURLを指定してアクセスした場合はモーダルではなく通常のページで表示したい。
※Reactなどについて詳しく解説はしません。
完成イメージ
使用技術
nodeとnpmは以下バージョンを使用しています。
$ node -v
v16.16.0
$ npm -v
8.11.0
reactなどは以下バージョンを使用しています。
"react": "17.0.2",
"react-router-dom": "6.2.1",
"tailwindcss": "3.0.23" // スタイリングのために使用しています。
サンプルの実装
コンポーネント
まずは、モーダルを表示するためのボタンを配置するページを用意します。
ボタンにはReact Router
から提供されているLink
コンポーネントを使用していきます。
通常モーダルを実装する際はuseState
などの状態管理を使用して実装しますが今回はルーティングでモーダルの表示非表示を管理していくので状態管理は必要ありません。
to
には対象のモーダルに付与したいURLを指定します。
state
には{ backgroundLocation: location }
を指定してください。こちらはコンテンツをモーダルとして表示するか通常のページとして表示するかの判定として使用される為重要な値となっています。
locationについては以下を参考にしてください。
参考: location
import { Link, useLocation } from "react-router-dom";
export const Home = () => {
const location = useLocation();
return (
<div className="p-8">
<button type="button">
<Link to="/image" state={{ backgroundLocation: location }}>
modal open
</Link>
</button>
</div>
);
};
続いて、モーダル表示用と通常のページで表示用のコンポーネントを作成します。今回は簡単に画像を表示するだけのものになっています。
import { useNavigate } from "react-router-dom";
export const Modal = () => {
const navigate = useNavigate();
const handleClose = () => {
navigate("/");
};
return (
<div className="fixed top-0 left-0 h-screen w-screen bg-black bg-opacity-10 pt-28">
<div className="absolute top-28 left-1/2 inline-block max-h-full w-11/12 -translate-x-1/2 transform rounded-lg bg-white">
<div className="relative p-6">
<p className="mb-4">モーダル</p>
<img
src="https://images.unsplash.com/photo-1657266175529-1fd6a225d4a4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMTA3MjV8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NTk3MDg4NDk&ixlib=rb-1.2.1&q=80&w=1080"
alt=""
className="h-52 w-full rounded-lg object-cover"
/>
<button
type="button"
onClick={handleClose}
className=" absolute top-4 right-4 rounded-lg bg-primary text-center font-semibold text-white shadow-md hover:bg-primary-dark"
>
<svg className="cursor-pointer h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
);
};
export const ImageView = () => (
<div className="p-8">
<p className="mb-4">通常ページ</p>
<img
src="https://images.unsplash.com/photo-1657266175529-1fd6a225d4a4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwzMTA3MjV8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NTk3MDg4NDk&ixlib=rb-1.2.1&q=80&w=1080"
alt=""
className="h-52 w-full rounded-lg object-cover"
/>
</div>
);
ルーティング
最後にルーティングを設定します。
useLocation()
でlocation
を取得し、さらにlocation
からLink
コンポーネントで渡したstate.backgroundLocation
を取得します。
そして、Routers
のlocation
というプロパティに取得したlocation,backgroundLocation
を渡します。
import { Route, Routes, useLocation } from "react-router-dom";
import { Home } from "pages/Home";
import { Modal } from "pages/Modal";
import { ImageView } from "pages/ImageView";
export const AppRouter = () => {
const location = useLocation();
const backgroundLocation = location.state.backgroundLocation;
return (
<>
<Routes location={backgroundLocation || location}>
<Route path="/" element={<Home />} />
<Route path="/image" element={<ImageView />} />
</Routes>
</>
);
};
backgroundLocation
がある場合のルーティングも記述します。
二つRouters
を記述することでモーダルの表示とモーダルの背面に表示される画面をルーティングすることができます。
~~略~~
{backgroundLocation && (
<Routes>
<Route path="/image" element={<Modal />} />
</Routes>
)}
動作確認
/homeにアクセスしボタンを押下するとURLが/imageに変わりモーダルが表示されます。モーダルと閉じると/homeへ変わりモーダルが非表示になります。
次に、このモーダルのURLをコピーして別タブで開きます。すると、モーダルではなく通常のページで上記のモーダルと同様のコンテンツが表示されます。
終わりに
以上で要件通りのモーダル実装完了です!
react-router
のv5とv6で記述方法が変わっているのでバージョンにはお気をつけてください!
少しでも参考になれば幸いです。
参考