ReactRouterでモーダルウィンドウを表示させる

# はじめに
主は普段Javaでバックエンド開発を行っているのですが、
最近フロントの技術でReactに入門しました。
理由としては、モダンなフロントの技術や知識を少しは身につけておいた方がいいかなと思ったのが理由になります。

入門時にはReactの公式ドキュメントがとても参考になり、
手も動かすことが出来るのでお勧めです。:thumbsup:

https://qiita.com/ooo-lang/items/e7f0ff0b9ba5bb482065

# ステップアップとしてReactRouterを学習
とりあえずはレベルでReactは書けるようになったけど、
まだ、フロントの技術として必要なのは**ルーティング機能**と**状態管理**

ということで、以下の記事で大体のReactRouterの機能を学習してみました。

https://reffect.co.jp/react/react-router-6/

学習の流れはインプット→アウトプットということで、とりあえずなんか真似て作ってみます。
それで理解が難しいところなどは自分なりに解説する事で知識定着を試みてます。

# 作ってみました:point_up:
なんやかんやで、Reduxも学習して今回のに組み込んでます。

https://github.com/ooo-lang/react-router-modal

## 環境
* Windows
* Vite
* Redux
* ReactRouter

## フォルダ構成
```
─src
    │  App.css
    │  App.jsx
    │  index.css
    │  main.jsx
    │
    ├─components
    │      modal.jsx
    ├─hooks
    │      useModalRoute.jsx
    ├─pages
    │      modal_next.jsx
    │      modal_top.jsx
    │      next.jsx
    │      top.jsx
    └─store
            index.js
```

## ソース

一部だけですが、ソースの中身になります。
App.jsxでは主にルートの定義を行っています。

```react:App.jsx
import { Route, Routes, useLocation } from 'react-router-dom'
import Modal from './components/modal';
import Next from './pages/next';
import Top from './pages/top';
import ModalNext from './pages/modal_next';
import ModalTop from './pages/modal_top';
import './App.css'

function App() {
  const location = useLocation(); // ➀
  const background = location.state?.background; // ➁
  return (
    <>
      <Routes location={background || location}>
        <Route path='/next' element={<Next />} />
        <Route path='/' element={<Top />} />
      </Routes>
      {background && (                           // ➂ 
        <Routes>
          <Route path='/' element={<Modal />}>
            <Route path='/modal-top' element={<ModalTop />} />
            <Route path='/modal-next' element={<ModalNext />} />
          </Route>  
        </Routes>
      )}
    </>
  )
}

export default App
```


**① ReactRouterで場所の管理**
ReactRouterでURLの場所の定義を管理します。
uselocationで持ってるlocationオブジェクトにて現在いるWebページの場所を定義する事が出来ます。

**② locationオブジェクトに追加情報を追加** 
uselocationのlocationオブジェクトのstateにはユーザー指定の情報を定義することができます。
ここでは、モーダルを表示した際のWebページの場所を記録しておきます。

```
interface Location {
  pathname: string;
  search: string;
  hash: string;
  state: unknown;  // URLに含めたくない情報などを格納するのに便利
  key: string;
}
```

**③ background && ...** 
background情報がある場合は新たなルーティング設定が適用されます。
条件が `true (真)` である場合に右側のコードブロックを実行するために使われています。

```react:useModalRoute.jsx
import { useLocation, useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';

const useModalRoute = () => {
  const navigate = useNavigate(); // ➀
  const location = useLocation();
  const dispatch = useDispatch(); // ➁
  const background = useSelector((state) => state.background); // ➂

  const startModalPath = (to) => {
    dispatch({
      type: 'CHANGE_LOCATION',
      payload: location,
    });
    navigate(to, {state: { background: location } }); // ➃
  };

  const endModalPath = () => {
    navigate(`${background?.pathname.replace(/\/+$/, "")}/${background?.search}`); // ➄
  }
  
  const goModalPath = (to) => {
    navigate(to, { state: { background: background } }); // ➅
  };

  return { startModalPath, endModalPath, goModalPath };
}

export default useModalRoute;
```
**➀ コード内でURL先に遷移**
コード内でURL先に遷移できるようにするために、useNavigate()を宣言します。

**➁ Reducerに対してActionを伝える**
StoreにあるReducerに対してActionを伝えるために、useDispatch()を宣言します。
伝達するAction.typeはlocationを更新する `'CHANGE_LOCATION'` です。
更新するのはReducerになります。

:::note info
useDispatch()が値を更新するのではなく、ただReducerに現在のStateの状態とActionを伝達するだけです
:::

**➂ useSelector()でStoreのStateにアクセス**
useSelector()でStoreのStateにアクセスします。

**➃ 遷移先に値を設定**
モーダルウィンドウを表示するボタンが押された時に、
startModalPathメソッドが走りモーダルウィンドウへ画面遷移します。

navigateにて遷移すると同時に遷移先のlocationオブジェクトに
`background`でユーザー定義を設けて、
モーダルウィンドウ表示中に裏で表示される画面のlocation情報を設定します。

**➄ Stateのbackground情報で元の画面へ戻る**
モーダル表示を終了する際に、Stateで定義しているbackgroundのlocation情報を元に
URLを組み替えています。
そのままnavigateで元の画面へ遷移します。

**➅ 遷移先にもlocation情報を渡す**
モーダルウィンドウ上で画面遷移する際に、次の画面にもnavigateでbackground情報を渡してあげる。

#### ざっくりとRedux解説
参考資料では状態管理ライブラリでRecoilを使用していますが、Reduxを学習するために
今回はReduxに置き換えました。
```javascript:store/index.js
import { createStore } from "redux";

const initialState = {
  background: undefined,
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'CHANGE_LOCATION':
      return {
        ...state,
        background: action.payload
      };
    default:
      return state;
  }
}

const sotre = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

export default sotre;
```

**`Store：`**
状態を保存してある倉庫のようなもの。
**`Reducer：`**
状態を更新するもの。Reducerだけが状態を更新できます。
**`Dispatch：`**
Reducerに現在のStateとActionを知らせるだけのもの。
**`Action：`**
いわゆるメソッド？を定義する。
ReducerはActionのTypeに定義されたアクションをもとにStateに対してアプローチする。
**`State：`**
状態のこと。Store内で管理されています。

全部のソースを載せるのは少し長くなってしまうので、ここまでにさせてください。
残りはGithub上にあります。

https://github.com/ooo-lang/react-router-modal

# おわりに
今回はReactでモーダルウィンドウを表示させるものを参考資料をもとに作成してみました。自分なりに参考資料を見て理解しにくかったところなどを解説する事で処理の流れなどをより理解できました。

Locationオブジェクトを使用して、モーダルウィンドウの表示元を記憶させたり、受け渡したりとLocationオブジェクトの構造を利用する仕組みに触れられたのは良かったです。

参考資料の方が全体を解説しているので気になる方は見てみてください。

# 参考資料

https://www.ey-office.com/blog_archive/2022/09/28/page-transition-on-modal-window-with-react-router/

https://developer.mozilla.org/ja/docs/Web/API/Location