LoginSignup
0
0

More than 1 year has passed since last update.

React Router in TypeScriptを利用してパブリックルートとプライベートルートを作成する方法

Posted at

React RouterはURLとコンポーネントを紐づけるライブラリです。React Routerを利用してパブリックルートとプライベートルートを作成する方法について解説します。

認証付きのReactアプリケーションを開発する場合、パブリックルートとプライベートルートが必要になる場合があります。まず、それらが何であるかを見ていきます。

Public Routes

パブリックルートは、アプリにサインイン前のみアクセスできるルートです。そのルートに該当するページコンポーネントは、例えばサインアップページ、パスワードを忘れた場合のページなどです。

Private Routes

プライベートルートは、アプリにサインイン後のみアクセスできるルートです。アプリによって様々ですが、そのルートに該当するページコンポーネントは、ユーザープロファイルページ、アプリ設定ページなどです。

パブリックルートとプライベートルートの制約は、サインイン前にプライベートルートにアクセスしてはならない、サインイン後にパブリックルートにアクセスしてはならないということです。

それでは、パブリックルートとプライベートルートを作成してみましょう。

Public Routes

まず、以下のようにパブリックルートの状態を処理するPublicRouteコンポーネントを作成しましょう。

PublicRoute.tsx
import { memo, ReactNode, VFC } from 'react';
import { Route, Redirect, RouteProps } from 'react-router-dom';

type Props = {
    children: ReactNode;
    isAuthenticated: boolean;
    redirectPath: string;
} & RouteProps;

export const PublicRoute: VFC<Props> = memo((props) => {
    const { children, isAuthenticated, redirectPath, ...routeProps } = props;
    return (
        <Route
            {...routeProps}
            render={({ location }) => (
                !isAuthenticated
                    ? (children)
                    : (
                        <Redirect to={{
                            pathname: redirectPath,
                            state: { from: location }}
                        }/>
                      )
            )}
        />
    );
});

上記のコードでわかるように、パブリックルートコンポーネントは、children, isAuthenticated, redirectPath, ...routePropsのpropsを受け取ります。

ユーザーが認証済の場合、ユーザーはredirectPathにリダイレクトされ、未認証の場合のみパブリックルートにアクセスできます。

Private Routes

一方、プライベートルートコンポーネントはパブリックルートに似ていますが、リダイレクトの条件が違います。

ユーザーが未認証の場合、ユーザーはredirectPathにリダイレクトされ、認証済の場合のみ、プライベートルートにアクセスできます。

PrivateRoute.tsx
import { memo, ReactNode, VFC } from 'react';
import { Route, Redirect, RouteProps } from 'react-router-dom';

type Props = {
    children: ReactNode;
    isAuthenticated: boolean;
    redirectPath: string;
} & RouteProps;

export const PrivateRoute: VFC<Props> = memo((props) => {
    const { children, isAuthenticated, redirectPath, ...routeProps } = props;
    return (
        <Route
            {...routeProps}
            render={({ location }) => (
                isAuthenticated
                    ? (children)
                    : (
                        <Redirect to={{
                            pathname: redirectPath,
                            state: { from: location }}
                        }/>
                      )
            )}
        />
    );
});

Integrating Routes

最後に、以下のようにルートコンポーネントをRouterコンポーネントに統合しましょう。

Router.tsx
import { memo, VFC } from "react";
import { Route, Switch, RouteProps } from "react-router-dom";

import { PageNotFound } from "components/pages/PageNotFound";
import { PageTop } from "components/pages/PageTop";
import { PublicRoute } from "router/PublicRoute"
import { PrivateRoute } from "router/PrivateRoute";
import { useGetToken } from "hooks/user/useGetToken";
import { PageSignIn } from "components/pages/PageSignIn";
import { PageSignUp } from "components/pages/PageSignUp";
import { PageForgot } from "components/pages/PageForgot";
import { PageTodo } from "components/pages/PageTodo";
import { PageSetting } from "components/pages/PageSetting";
import { PageHealthCheck } from "components/pages/PageHealthCheck";
import { TodosProvider } from "providers/TodosProvider";
import { PageResetPassword } from "components/pages/PageResetPassword";

export type PublicRouteProps = {
    isAuthenticated: boolean;
    redirectPath: string;
} & RouteProps;

export type PrivateRouteProps = {
    isAuthenticated: boolean;
    redirectPath: string;
} & RouteProps;

export const Router: VFC = memo(() => {
    const { isAuthenticated } = useGetToken();

    const defaultPublicRouteProps: PublicRouteProps = {
        isAuthenticated: isAuthenticated,
        redirectPath: '/todo',
    };

    const defaultPrivateRouteProps: PrivateRouteProps = {
        isAuthenticated: isAuthenticated,
        redirectPath: '/sign_in',
    };

    return (
        <TodosProvider>
            <Switch>
                <PublicRoute
                    {...defaultPublicRouteProps}
                    exact
                    path="/"
                    sensitive
                >
                    <PageTop />
                </PublicRoute>
                <PublicRoute
                    {...defaultPublicRouteProps}
                    exact
                    path="/sign_in"
                    sensitive
                >
                    <PageSignIn />
                </PublicRoute>
                <PublicRoute
                    {...defaultPublicRouteProps}
                    exact
                    path="/sign_up"
                    sensitive
                >
                    <PageSignUp />
                </PublicRoute>
                <PublicRoute
                    {...defaultPublicRouteProps}
                    exact
                    path="/user/forgot"
                    sensitive
                >
                    <PageForgot />
                </PublicRoute>
                <PublicRoute
                    {...defaultPublicRouteProps}
                    exact
                    path="/user/reset_password"
                    sensitive
                >
                    <PageResetPassword />
                </PublicRoute>
                <PrivateRoute
                    {...defaultPrivateRouteProps}
                    exact
                    path="/todo"
                    sensitive
                >
                    <PageTodo />
                </PrivateRoute>
                <PrivateRoute
                    {...defaultPrivateRouteProps}
                    exact
                    path="/user/settings"
                    sensitive
                >
                    <PageSetting />
                </PrivateRoute>
                <Route path="*">
                    <PageNotFound />
                </Route>
            </Switch>
        </TodosProvider>
    );
});

認証ステータスisAuthenticatedはuseGetToken()関数から受け取ります。

パブリックルートのリダイレクトパスを/todo、プライベートルートのリダイレクトパスを/sign_inに設定しました。

未認証ルートをPublicRouteコンポーネントでラップし、認証済ルートをPrivateRouteコンポーネントでラップしました。
これで、パブリックルートとプライベートルートが構成されました。一致するものがない場合、PageNotFoundがレンダリングされます。

まとめ

Routerコンポーネントはアプリで使用するURLとそのURLに紐ずくページコンポーネントが全て記述されています。また認証によるリダイレクト先のURLも記述されています。メンテナンス性の高いコードを実現しています。

以上、お役に立てれば幸いです。

ソースコードをGitHubへアップしました。今回解説した内容はGitHubへアップしたSingle Page Applicationの一部分です。

参考文献

この記事は以下の情報を参考にして執筆しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0