ReactRouterを使ってGoogleログイン前と後で画面を変える(続き)

前回前々回に引き続き、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 =>propsroute props というものであり、 route propsの location でアクセスされたパスを取得できる。つまり、今回は /private という値が取れる。この値を <Redirect to= に指定するオブジェクトの state 経由で渡す( to: object)。

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 )へ遷移
    • ログイン後: プライベート画面を表示

おしまい

こんな感じでいいのかな。ベストプラクティスのようなものあったら知りたいのだけど見つけられなかった。次回はサーバーサイドアプリケーションとのセッション管理を考える。

こういうコードになりました。