背景
Reactのアプリケーションを書く場面があり、ログイン系でありがちな
- ログインしていないければ
/login
に返す - ログイン成功したらどこかに移動する
というパターンをやりたくなりました。
reactでのURLのパスとコンポーネントの対応をつけるときにはreact-router-dom
というのを使うのが定番なようですが、
このreact-router-dom
では、<Route>
というタグを利用してルーティングを設定する事ができます。
また<Redirect>
というタグを使う事でページ感のリダイレクトを実行させる事もできます。
そのため、ログイン状態に応じてRedirectを返すか、コンポーネントのコンテンツ自体を表示するかの分岐を<Route>
内のコンポーネントに記述してやれば
未ログイン時のリダイレクト処理は実現する事可能です。
しかし、各コンポーネントで毎度そのような記述をするのはボイラープレート的なコードが増えてしまいます。
react-router-domの公式ドキュメントに、そのような状況に対処するためのRoute
タグのラッパー PrivateRoute
のJavascriptでの作成例があるのですが、
今回のプロジェクトはTypeScriptで記述しているため、せっかくなのでTypeScriptで記述しておきたいところでした。
環境
- react-router-dom: ^5.1.2
コード
import React from 'react' import { Route, RouteProps, Redirect } from 'react-router-dom' import AuthState from './AuthState'; interface IProps extends RouteProps { loginPath: string; authState: AuthState; } const PrivateRoute: React.FC<IProps> = props => { const { children: Children, component: Component, ...rest } = props; return ( <Route {...rest} render={innerProps => { if (props.authState === AuthState.Unauthorized) { return (<Redirect to={{ pathname: props.loginPath, state: { from: innerProps.location } }} />); } else if (props.authState === AuthState.Authorized) { if (Children instanceof Function) { return Children(innerProps); } else if (Children != null) { return Children; } else if (Component != null) { return React.createElement(Component, innerProps) } else { return null; } } } } /> ); } export default PrivateRoute;
使い方
<PrivateRoute path="/test" loginPath="/login" authState={this.state.authState} component={Hello}/>
または
<PrivateRoute path="/test" loginPath="/login" authState={this.state.authState}> <Hello /> </PrivateRoute>
つまづいた所
children
component
render
の優先度
Routeタグでは props
として 子タグとしてchildren
や component
、そしてインラインレンダリングとしてrender
関数など
いくつかのプロパティでどのようなコンポーネントを表示するかを指定できますが
優先度として children
> component
> render
となっておりchildren
やcomponent
が指定されているとrender
は無視されてしまいます。
(これについてはconsoleを見ていると警告が出ます)
今回はRoute
タグをラップして、Route
タグのrender
関数でRedirect
タグを吐きだすか、元々指定されたコンポーネントをレンダリングするかの分岐を実施したく、
また、Route
タグから簡単に置きかえられるように挙動はあまり変えたくありません。
そのため、まず一旦props
からchildren
とcomponent
のプロパティを剥がしておいた上で、render
関数の中で適切に優先度を保ったまま呼びだしてやる必要がありました。
参考
- React Router: Declarative Routing for React.js
- react-router/Route.js at f31bb27aa61dd4cb1c3cd9aa78133f739e2e9bb9 · ReactTraining/react-router · GitHub
おまけ
ログイン成功してたらメインページへ
PrivateRoute
の分岐を逆転させるだけでできます。
import React from 'react' import { Route, RouteProps, Redirect } from 'react-router-dom' import AuthState from './AuthState'; interface IProps extends RouteProps { mainPath: string; authState: AuthState; } const AuthenticateRoute: React.FC<IProps> = props => { const { children: Children, component: Component, ...rest } = props; return ( <Route {...rest} render={innerProps => { if (props.authState === AuthState.Authorized) { return (<Redirect to={{ pathname: props.mainPath, state: { from: innerProps.location } }} />); } else if (props.authState === AuthState.Unauthorized) { if (Children instanceof Function) { return Children(innerProps); } else if (Children != null) { return Children; } else if (Component != null) { return React.createElement(Component, innerProps) } else { return null; } } } } /> ); } export default AuthenticateRoute;