React Router V6 で認証状態によるリダイレクト

Firebase Authentication で認証状態になったときだけ閲覧できるページを作るという記事で React Router V5 のときの認証状態によるリダイレクトを実装したが、V6 になって同じ書き方ではできなくなったので、公式サイトの方法で実装する。

開発環境

  • react: 17.0.2
  • react-router-dom: 6.2.1

create-react-app を使ったプロジェクトを前提とする。

本文

Auth Example から必要な部分のみ抜き出して実装する。

認証状態の判定フック

前提として、認証状態を true か false で返してくる API が存在しているとする。

src/components/middleware/Auth/useAuth.ts

import { useState, useEffect } from 'react';
import authRepository from 'repositories/authRepository';

export const useAuth = () => {
    const [check, setCheck] = useState({ checked: false, isAuthenticated: false });

    useEffect(() => {
        // 認証状態チェック API にアクセスする関数を別途作成済とする
        authRepository.check()
            .then(res => {
                // res.data.is_authenticated には true/false が入る
                setCheck({ checked: true, isAuthenticated: res.data.is_authenticated });
            })
            .catch((e) => {
                setCheck({ checked: false, isAuthenticated: false });
            });
    }, []);

    return [check];
}

このように「認証状態のチェックが終わったか」と「認証状態」の情報を取得するためのフックを作成する。

認証状態によるリダイレクトコンポーネント

作成したフックを用いて、認証状態のときのみ閲覧できる画面用の RequireAuth と、非認証状態のときのみ閲覧できる画面用の RequireGuest を作成する。
認証状態のチェックが終わっていないときは “Loading…” という文字列を表示するものとする。

src/components/middleware/Auth/Auth.tsx

import { useLocation, Navigate } from "react-router-dom";
import { useAuth } from "components/middleware/Auth/useAuth";

// 認証状態のときのみ閲覧できる画面用
export function RequireAuth({ children }: { children: JSX.Element }) {
    let location = useLocation();
    const [check] = useAuth();

    if (!check.checked) {
        return (
            <div>Loading...</div>
        );
    }

    if (check.isAuthenticated) {
        return children;
    }

    return <Navigate to="/login" state={{ from: location }} replace />;
}

// 非認証状態のときのみ閲覧できる画面用
export function RequireGuest({ children }: { children: JSX.Element }) {
    const [check] = useAuth();

    if (!check.checked) {
        return (
            <div>Loading...</div>
        );
    }

    if (check.isAuthenticated) {
        return <Navigate to="/home" replace />;
    }

    return children;
}

ルーティング

React Router V6 の公式の例によると一つ一つの Route 内の element を認証状態によるリダイレクトコンポーネントで包むことになるらしい。

src/App.tsx

import {
  Routes,
  Route
} from "react-router-dom";
import { RequireAuth, RequireGuest } from "components/middleware/Auth/Auth";
import Index from 'components/page/Index/Index';
import Home from 'components/page/Home/Home';
import Login from 'components/page/Login/Login';

export default function App() {
  return (
    <Routes>
      // いつでも閲覧できる画面
      <Route path="/" element={<Index />} />
      // 非認証状態の場合のみ閲覧できる画面
      <Route path="/login" element={<RequireGuest><Login /></RequireGuest>} />
      // 認証状態の場合のみ閲覧できる画面
      <Route path="/home" element={<RequireAuth><Home /></RequireAuth>} />
    </Routes>
  );
}

あとがき

一つ一つの element を包むのは記述量が多すぎると思うのだが、作成した RequireAuth/RequireGuest は Route オブジェクトではないのでこの方法でしか使用できない。

V5 のときのように複数のルートを纏めて包む方法については公式サイトには記載がなかったため、今後方法がわかったらまたブログに記載する。