Open6

react-router-dom v6 でのroute入れ子に引っかかったからメモ

EverydayNewbieEverydayNewbie

ChakraUIを導入し、react-router-domでルーティングをする
という練習の一貫で引っかかった

App.tsx

function App() {
  return (
    <ChakraProvider theme={theme}>
      <BrowserRouter>
        <Router />
      </BrowserRouter>
    </ChakraProvider>
  );
}

Router.tsx

export const Router: VFC = memo(() => {
  return (
    <Routes>
      <Route path="/" element={<Login />} />
      <Route path="home/*" element={<Home />}>
        <Route path="setting" element={<Setting />} />
        <Route path="management" element={<Management />} />
      </Route>
      <Route path="/*" element={<Page404 />} />
    </Routes>
  );
});

こんな感じでコーディングしていた

やりたいのは、

  • まずそのページを開いたら、Login画面を開きたい
  • /homeでHome画面へ”遷移”したい
  • /home/setting や /home/managementでそれぞれSettingページや、Managementページへ”遷移”したい

参考にしていた教材がreact router dom v5の内容だったので、自力でv6の内容を調べてアップデートしていった

・SwitchではなくRoutesを利用する
・v5ではRouteのchildrenにネスト先のコンポーネントを入れるがv6では、element というのを使ってネスト先のコンポーネントを入れる

EverydayNewbieEverydayNewbie

ただし、試しに実行してみてもどうしても
localhost:3000/home
でHomeのページが見れても、例えば
localhost:3000/home/setting
でSettingページの内容が表示されない。。。。

書き方は間違っていないはずなのに、、、、

EverydayNewbieEverydayNewbie

結論:ページ丸々遷移したいなら、Routeを並列で書いて、パスもきっちり書く。特定ページ(例えばHome内)でタブ切り替え的な感じで部分的にコンテンツ切り替えしたいならOutletを使う。

ページ丸々遷移したいケース

冒頭に書いた例

Router.tsx

export const Router: VFC = memo(() => {
  return (
    <Routes>
      <Route path="/" element={<Login />} />
      <Route path="home/*" element={<Home />}>
        <Route path="setting" element={<Setting />} />
        <Route path="management" element={<Management />} />
      </Route>
      <Route path="/*" element={<Page404 />} />
    </Routes>
  );
});

書き直すなら

Router.tsx

export const Router: VFC = memo(() => {
  return (
    <Routes>
      <Route path="/" element={<Login />} />
      <Route path="home" element={<Home />} />
      <Route path="home/setting" element={<Setting />} />
      <Route path="home/management" element={<Management />} />
      <Route path="/*" element={<Page404 />} />
    </Routes>
  );
});

↑これで想像どおり動く
まあ、単純に考えるとこうなるよねって感じ。

どうやら、Routeの仕様上、Homeにヒットした時点でまずその子は表示されないらしい
つまり、home/settingと入力しても、homeが優先されるみたいな。

※それに加えて、冷静に考えて、RoutesやRouteも一つ一つがコンポーネントだから、あるコンポーネントの中で入れ子形式子のコンポーネント書いたら、本来は親コンポーネントを表示しつつ子コンポーネントを表示するっていうReact書くなら当たり前の事なのに、RouteでURLとかパスが絡んでくると、URLの入れ子ばかり気にしちゃって、そういうの忘れるよな〜〜(Routeは子は表示されない仕様ってだけで、Homeページが表示され続けるってある意味正常な動きだよねと。V5は知らぬ)(V5の時代は、URLをネスト構造的に捉えていたのに対して、V6は本来のコンポーネントの形に戻ってる印象(事実は知らない。個人の所感。))

EverydayNewbieEverydayNewbie

↑これらをちょっと効率的に書くなら、
route.tsxとして別ファイルに切り出して、
{
path : 略
element : 略
}
って感じで、オブジェクト型で遷移を定義し、それが複数あるならそれをオブジェクト型の配列として持たせ、
Router.tsx側で、map使って展開するとか良いかも
※試してないけど

EverydayNewbieEverydayNewbie

Homeページ内で、URL切り替えることで、一部コンテンツだけ表示を変えたいケース

冒頭に書いた例

Router.tsx

export const Router: VFC = memo(() => {
  return (
    <Routes>
      <Route path="/" element={<Login />} />
      <Route path="home/*" element={<Home />}>
        <Route path="setting" element={<Setting />} />
        <Route path="management" element={<Management />} />
      </Route>
      <Route path="/*" element={<Page404 />} />
    </Routes>
  );
});

書き直すなら

Router.tsx

export const Router: VFC = memo(() => {
  return (
    <Routes>
      <Route path="/" element={<Login />} />
      <Route path="home" element={<Home />} />
        <Route path="setting" element={<Setting />} />
        <Route path="management" element={<Management />} />
      </Route>
      <Route path="/*" element={<Page404 />} />
    </Routes>
  );
});

親に該当するコンポーネント(このケースではHomeが該当する)にOutletを入れる
Home.tsx

import {VFC, memo} from "react";
export const Home: VFC = memo(() => {
return(<div> <p>This is Home Page</p> <Outlet /> </div>)
});

みたいな感じ
こうすると、<Outlet />の部分が、Routerの入れ子部分のSettingとかManagementの部分をURLによって切り替わる様になる。
※もちろん、この場合はHomeコンポーネント内でのSettingとManagementの切り替えなので、「This is Home Page」というHomeコンポーネントそのものに記載されている文章は表示され続ける