はじめに
react-router-domでPrivateRouteを実現します。今回は以下の機能を実装していきたいと思います。
- ログイン済みユーザーしかアクセスできないページを作る
- ↑のページに、ログインせずにアクセスしようとするとログインページにリダイレクトされる。
- 2でログインページにリダイレクトされた後、ログインを行えば元のページ(アクセスしたかったページ)に遷移する。
画面構成は以下のようにします。
-
/login
: ログイン画面。未ログインユーザーしかアクセスできない。 -
/
: ホーム画面。ログイン済みユーザーしかアクセスできない。 -
/profile/:userId
: ユーザーのプロフィール画面。ログイン済みユーザーしかアクセスできない。
プロジェクトはGithubのレポジトリにあげているのでよければご覧ください。
プロジェクトを作る
今回はcreate-react-appを用いてセットアップします。次にreact-router-dom
をインストールします
npx create-react-app private-router-sample --template typescript
npm install react-router-dom @types/react-router-dom
PrivateRouteを実装する
今回の肝となるPrivateRouteの実装です。(useAuthUser
, AuthUserProvider
については別ファイルで定義しています。詳しい実装はGithubをご覧ください)
import React from 'react';
import { BrowserRouter as Router, Redirect, Route, RouteProps, Switch } from 'react-router-dom';
// PrivateRouteの実装
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
const authUser = useAuthUser()
const isAuthenticated = authUser != null //認証されているかの判定
if (isAuthenticated) {
return <Route {...props}/>
}else{
console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
return <Redirect to="/login"/>
}
}
const App: React.FC = () => {
return (
<AuthUserProvider>
<Router>
<Switch>
<Route exact path="/login" component={LoginPage}/>
{/* Routeと同じ形で使用 */}
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute exact path="/profile/:userId" component={ProfilePage}/>
</Switch>
</Router>
</AuthUserProvider>
);
}
ポイントは、PrivateRoute
内の以下の実装です。
if (isAuthenticated) {
return <Route {...props}/>
} else {
console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
return <Redirect to="/login"/>
}
認証済身ならRoute
を、認証されていなければRedirect
を返すことでログインページへのリダイレクトを行っています。
UnAuthRoute
上記の実装では、「未ログインのユーザーが/
にアクセス」することは防げましたが、「ログイン済みのユーザーが/login
にアクセス」することは防げていません。そこで、ログイン済みユーザーが/login
へアクセスした場合、/
へリダイレクトする処理を実装します。
今回はPrivateRoute
と逆の処理を行うUnAuthRoute
を実装したいと思います。
import React from 'react';
import { BrowserRouter as Router, Redirect, Route, RouteProps, Switch } from 'react-router-dom';
// PrivateRouteの実装
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
const authUser = useAuthUser()
const isAuthenticated = authUser != null //認証されているかの判定
if (isAuthenticated) {
return <Route {...props}/>
}else{
console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
return <Redirect to="/login"/>
}
}
// ===追加部分 ==========
// UnAuthRouteの実装
const UnAuthRoute: React.FC<RouteProps> = ({ ...props }) => {
const authUser = useAuthUser()
const isAuthenticated = authUser != null
if (isAuthenticated) {
console.log(`ログイン済みのユーザーは${props.path}へはアクセスできません`)
return <Redirect to="/" />
} else {
return <Route {...props} />
}
}
// ===追加部分ここまで ======
const App: React.FC = () => {
return (
<AuthUserProvider>
<Router>
<Switch>
{/* 未ログインユーザーのみアクセス可能 */}
<UnAuthRoute exact path="/login" component={LoginPage}/>
{/* ログイン済みユーザーのみアクセス可能 */}
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute exact path="/profile/:userId" component={ProfilePage}/>
</Switch>
</Router>
</AuthUserProvider>
);
}
元のページへリダイレクト
ここまでの実装では、3の要件を満たしていません。
例えば、未ログインのユーザーが/profile/1
にアクセスしたときの処理の流れは以下のようになります
1. /profile/1にアクセス
2. PrivateRouteによって/loginにリダイレクトされる
3. ログインが完了すればUnAuthRouteによって/にリダイレクトされる ← 要件を満たすならば`/profile/1`へリダイレクトされるべきです。
そこで、PrivateRoute
、UnAuthRoute
を以下のように修正します。
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
const authUser = useAuthUser()
const isAuthenticated = authUser != null
if (isAuthenticated) {
return <Route {...props}/>
}else{
console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
return <Redirect to={{pathname: "/login", state: { from: props.location?.pathname }}}/> // fromに本来アクセス
}
}
const UnAuthRoute: React.FC<RouteProps> = ({ ...props }) => {
const authUser = useAuthUser()
const isAuthenticated = authUser != null
const { from } = useLocation<{from: string | undefined}>().state
if (isAuthenticated) {
console.log(`ログイン済みのユーザーは${props.path}へはアクセスできません`)
return <Redirect to={from ?? "/"} />
} else {
return <Route {...props} />
}
}
実装のポイントはPrivateRouteの <Redirect to={{pathname: "/login", state: { from: props.location?.pathname }}}/>
です。PrivateRouteは未ログインユーザーをログインページにリダイレクトしますが、その際にユーザーがアクセスしようとしたURLをstateとして設定しています。
ここで設定したstateはUnAuthRouteで const { from } = useLocation<{from: string | undefined}>().state
という形で取り出されます。取り出されたURLは、return <Redirect to={from ?? "/"} />
でリダイレクト先として指定されます。(?? "/"
が指定されているのは、ユーザーが直接ログインページにアクセスした場合はfrom == null
となるからです)
最後に
今回はログイン/未ログインの2状態を扱いましたが、サービスによってはユーザー登録が絡む場合もあるでしょう。その場合には、未ログイン/未登録/登録済みの3状態を扱うことになりますが、UnRegisteredRoute
を追加するなどで応用できます。