【React】モーダルにURLを設定する

# はじめに
今回はReacr,React-Routerを使用し以下のような要件のモーダルを実装していきます。
- モーダルにURLを付与したい。（モーダルを表示している状態を別ページ扱いとしたい）
- 別のタブでURLを指定してアクセスした場合はモーダルではなく通常のページで表示したい。

※Reactなどについて詳しく解説はしません。
### 完成イメージ
![t.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697070/8ee3f3d6-dbbb-cb0f-59c9-0582b00352e9.gif)


### 使用技術
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](https://v5.reactrouter.com/web/api/location)
```Home.jsx
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>
  );
};
```
続いて、モーダル表示用と通常のページで表示用のコンポーネントを作成します。今回は簡単に画像を表示するだけのものになっています。
```Modal.jsx
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>
  );
};
```
```ImageView.jsx
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`を渡します。
```AppRouter.jsx
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`を記述することでモーダルの表示とモーダルの背面に表示される画面をルーティングすることができます。
```AppRouter.jsx
~~略~~
{backgroundLocation && (
  <Routes>
    <Route path="/image" element={<Modal />} />
  </Routes>
)}
```
# 動作確認
/homeにアクセスしボタンを押下するとURLが/imageに変わりモーダルが表示されます。モーダルと閉じると/homeへ変わりモーダルが非表示になります。
![スクリーンショット 2022-08-20 17.00.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697070/0d471485-f13a-ea55-06eb-121733a0f9c7.png)

次に、このモーダルのURLをコピーして別タブで開きます。すると、モーダルではなく通常のページで上記のモーダルと同様のコンテンツが表示されます。
![スクリーンショット 2022-08-20 17.00.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697070/4e30a3fb-76da-a2c0-911c-d50a63f0d2d0.png)

# 終わりに
以上で要件通りのモーダル実装完了です！
`react-router`のv5とv6で記述方法が変わっているのでバージョンにはお気をつけてください！
少しでも参考になれば幸いです。

### 参考
https://v5.reactrouter.com/web/example/modal-gallery

https://www.codedaily.io/tutorials/Create-a-Modal-Route-with-Link-and-Nav-State-in-React-Router