前回、前々回に引き続き、Google Sign-In をReactなSPAで使った時の日記です。
今回は、前回できなかった /login
と /private
のルーティングを実装する。
/login
- ログイン前: ログインページを表示 => ログインに成功すると
/
へ遷移 - ログイン後:
/
へ遷移
- ログイン前: ログインページを表示 => ログインに成功すると
/private
- ログイン前: ログインページへ遷移 => ログインに成功するとアクセスしたパス(
/private
)へ遷移 - ログイン後: プライベート画面を表示
- ログイン前: ログインページへ遷移 => ログインに成功するとアクセスしたパス(
前回終了時点のコード。
create-react-app
でプロジェクトを生成した後、 src/App.js
だけ編集してる。
/login
/login
をやってみる。
/login
- ログイン前状態: ログインページを表示 => ログインに成功すると
/
へ遷移 - ログイン後状態:
/
へ遷移
- ログイン前状態: ログインページを表示 => ログインに成功すると
現状だとログインに成功しても自動で /
に遷移しない。ReactRouterの <Redirect />
を使うと、自動で遷移するようにできる。
React Routerのドキュメント を読むと、 <Redirect>
を使ってナビゲーションしろ、と書いてある。
Login
コンポーネントにも authenticated
をpropsで渡して、true
だったら <Redirect to='/login' />
をrederしてTopページに遷移するようにする。
--- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; +import { BrowserRouter as Router, Switch, Route, Link, Redirect } from 'react-router-dom'; class App extends Component { state = { @@ -54,7 +54,7 @@ class App extends Component { <Top authenticated={this.state.authenticated} onSignOut={this.onSignOut} /> }/> <Route path="/login" render={props => - <Login onSignIn={this.onSignIn} gapi={this.state.gapi}/> + <Login onSignIn={this.onSignIn} gapi={this.state.gapi} authenticated={this.state.authenticated} /> }/> </Switch> </Router> @@ -74,6 +74,9 @@ class Login extends Component { } } render() { + if (this.props.authenticated) { + return (<Redirect to='/' />) + } return ( <div> <h1>ようこそ!</h1>
できた。 <Redirect />
を使った遷移は、historyに対してpushするのではなくreplaceするみたいなので、ブラウザの戻るボタンを押されても大丈夫。
https://reacttraining.com/react-router/web/api/Redirect
この修正で、ログイン後状態にアクセスした場合も対応できた。
以下のルーティングができた。
/login
- ログイン前: ログインページを表示 => ログインに成功すると
/
へ遷移 - ログイン後:
/
へ遷移
- ログイン前: ログインページを表示 => ログインに成功すると
未ログインだとアクセスできないページ
- /private
- ログイン前状態: ログインページへ遷移 => ログインに成功するとアクセスしたパス( /private )へ遷移
- ログイン後状態: プライベート画面を表示
ログイン前状態だとアクセスできないページをやる。ログイン成功時のリダイレクト先を動的にして、最初にアクセスしたパスに遷移させるのが難しそう。
ReactRouterのドキュメントに "Redirects (Auth)" というサンプル があったので、それを参考に <PrivateRoute>
というものを実装する。
--- a/src/App.js +++ b/src/App.js @@ -1,6 +1,24 @@ import React, { Component } from 'react'; import { BrowserRouter as Router, Switch, Route, Link, Redirect } from 'react-router-dom'; +const PrivateRoute = ({authenticated, render, ...rest}) => ( + authenticated ? ( + <Route {...rest} render={render} /> + ) : ( + <Route + {...rest} + render={props => + <Redirect + to={{ + pathname: "/login", + state: {from: props.location} + }} + /> + } + /> + ) +); + class App extends Component { state = { authenticated: false, @@ -51,10 +69,13 @@ class App extends Component { <Router> <Switch> <Route exact path="/" render={props => - <Top authenticated={this.state.authenticated} onSignOut={this.onSignOut} /> + <Top {...props} authenticated={this.state.authenticated} onSignOut={this.onSignOut} /> }/> <Route path="/login" render={props => - <Login onSignIn={this.onSignIn} gapi={this.state.gapi} authenticated={this.state.authenticated} /> + <Login {...props} onSignIn={this.onSignIn} gapi={this.state.gapi} authenticated={this.state.authenticated} /> + }/> + <PrivateRoute path="/private" authenticated={this.state.authenticated} render={props => + <div>プライベートなページ</div> }/> </Switch> </Router>
まず、 <PrivateRoute>
というのを作って、 <Switch>
の中のルーティングで使う。 <PrivateRoute>
に authenticated
を渡し、ログイン済みかどうかで分岐。
ログイン済みである場合
ログイン済みであれば、 <PrivateRoute>
の render
で渡された関数にルーティングする。今回の例だと <div>プライベートなページ</div>
をrenderする。
( <Routing>
に {...rest}
と指定することで、 <PrivateRoute>
に明記していないpropsをそのまま <Route>
にも引き継いでいる)
ログイン済みではない場合
ログイン済みではない場合、 <Redirect>
をrenderすることで /login
へ遷移させる。この時に重要なのが、 state: {from: props.location}
という指定。
<Route render={props =>
の props
は route props というものであり、 route propsの location
でアクセスされたパスを取得できる。つまり、今回は /private
という値が取れる。この値を <Redirect to=
に指定するオブジェクトの state
経由で渡す(
Redirect先のコンポーネントで渡された値( /private
)を取得できる。
@@ -75,7 +96,8 @@ class Login extends Component { } render() { if (this.props.authenticated) { - return (<Redirect to='/' />) + const { from } = this.props.location.state || { from: { pathname: "/" } }; + return (<Redirect to={from} />) } return ( <div>
<Login>
で const { from } = this.props.location.state || { from: { pathname: "/" } };
として、渡された最初にアクセスしたパス( /private
)を取得し、ログイン成功時にそのパスへ遷移させる。
これで以下のルーティングもできた。
/private
- ログイン前: ログインページへ遷移 => ログインに成功するとアクセスしたパス(
/private
)へ遷移 - ログイン後: プライベート画面を表示
- ログイン前: ログインページへ遷移 => ログインに成功するとアクセスしたパス(
おしまい
こんな感じでいいのかな。ベストプラクティスのようなものあったら知りたいのだけど見つけられなかった。次回はサーバーサイドアプリケーションとのセッション管理を考える。
こういうコードになりました。