React
react-router
react-router-dom

React-Router v4を使って認証状態でリダイレクトする処理

ログインが完了したらトップページにリダイレクト、未ログインで認証が必要なページにアクセスしたらログインページへリダイレクト、という処理がある。

react-routerのv4でルーティングするとこんな形である。

App.js
import React, {Component} from 'react'
import {
  BrowserRouter as Router,
  Route, Switch
} from 'react-router-dom'

import Auth from './containers/Auth'
import Top from './containers/Top'
import Login from './containers/Login'
import Signup from './containers/Signup'
import Page from './containers/Page'

class App extends React.Component{
  render(){
    return(
      <Router>
        <Switch>
          <Route exact path="/login" component={Login}/>
          <Route exact path="/signup" component={Signup}/>
          <Auth>
            <Switch>
              <Route exact path="/" component={Top}/>
              <Route exact path="/page" component={Page}/>
            </Switch>
          </Auth>
        </Switch>
      </Router>
    )}
}

export default App

このとき、Authコンポーネントでは

1. 認証済みなら何もせず、配下のルーティングに処理をプロキシする(バケツリレーする)
2. 認証がされていない(未ログイン)なら /login ページにリダイレクトする
ということをする。

stateの props.currentUser.isLoggedIn に認証状態を保持しているとして、

components/Auth.js
import React from 'react'
import { Redirect } from 'react-router-dom'

const Auth = (props) => (props.currentUser.isLoggedIn ? props.children : <Redirect to={'/Login'}/>)

export default Auth

結果は非常にシンプルなのだが、1. のバケツリレーがうまくいかず手間取った。NG集としては、

const Auth = (props) => (props.currentUser.isLoggedIn ? <Route children={props.children} /> : <Redirect to={'/Login'}/>)
const Auth = (props) => (props.currentUser.isLoggedIn ? <Route {...props} /> : <Redirect to={'/Login'}/>)

のように、プロキシのためにRouteコンポーネントを介すべきと思っていたら、これでは認証後のページでリダイレクトループが発生してしまう。props.children自体が、App.jsでAuthコンポーネントでラップしているSwitchコンポーネントだと気づいてうまくいった。

なお、Authコンポーネントの直下は単一のコンポーネントにしておく必要がある。今回はSwitchコンポーネントが該当する。バケツリレーするprops.childrenが直下に複数のコンポーネントを持ってしまうと、renderされるコンポーネントは単一の要素でなければいけないというreactの制約に怒られてしまうからだ。