react.js
reactjs
React
react-router
redux

react-router v4 のログイン処理

More than 1 year has passed since last update.

経緯

最近react、reduxでwebアプリケーションを作る機会があり、どうせなら新しいものを使おうとreact-routerのversion4を使いました。
versionが上がるたびに大きく内容が変わるreact-routerで最近でたversionということもあり日本語のドキュメントが少なかったので自分へのメモの意味を込めて投稿しました。
react、reduxを触るのが今回初の初心者なので間違っている箇所があれば指摘をお願いします。

実装

react-routerはv4になってpackagingが変わりました。
詳細は下記を参照してください。
https://github.com/ReactTraining/react-router
今回はwebなのでreact-router-domを使用します。

routes.jsx
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import App from './components/App';
import Sample from './components/Sample';
import Auth from './containers/Auth';
import Login from './containers/Login';

const routes = () => (
  <BrowserRouter>
    <App>
      <Switch>
        <Route exact path='/login' component={Login} />
        <Auth>
          <Switch>
            <Route path="/" component={Sample} />
          </Switch>
        </Auth>
      </Switch>
    </App>
  </BrowserRouter>
);

export default routes;

routingを記入しているファイルです。
v4から追加された <Switch> というcomponentを使用しています。Switchは一致するpathを排他的にレンダリングしてくれるものらしいです。
/ でアクセスされた場合、Sampleコンポーネントのみをレンダリングするようにしています。

App.jsx
const App = () => (
  <div>
    <Route children={this.props.children} />
  </div>
)

export default App

全体の共通処理とかを記述する用です。
認証チェックはlogin画面ではしたくないのでここには記述していません。

Auth.jsx
class Auth extends Component {
  static PropTypes = {
    user: PropTypes.string
  }

  componentWillMount() {
    this.userWillTransfer(this.props);
  }

  componentWillUpdate(nextProps) {
    this.userWillTransfer(this.props);
  }

  userWillTransfer(props) {
    if (!localStorage.getItem('sessionId')) {
      this.setState({ isAuthenticated: false });
    } else {
      this.setState({ isAuthenticated: true });
    }
  }

  render() {
    return (
      this.state.isAuthenticated? (
        <Route children={this.props.children} />
      ) : (
        <Redirect to={'/login'} />
      )
    )
  }
}

const mapStateToProps = state => ({
  sessionId: state.sessionId
});

export default withRouter(connect(mapStateToProps)(Auth));

ここで認証済みか否かをチェックします。
componentWillMountcomponentWillUpdateを使い画面が表示されるときと画面が更新された際にログイン済みか否かをチェックしています。
今回はlocalstrageにsessionIdがあるかないかでチェックしています。
認証済みの場合はアクセスされたpathにそのまま遷移させ、認証されていなかったらログイン画面にリダイレクトさせています。

Login.jsx
class Login extends Component {

  static propTypes = {
    sessionId: PropTypes.string,
    message: PropTypes.string,
    dispatch: PropTypes.func.isRequired,
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.sessionId) {
      this.setState({ sessionId: nextProps.sessionId });
      localStorage.setItem('sessionId', this.state.sessionId);
    } else {
      this.setState({ message: nextProps.message });
    }
  }

  onLogin(e) {
    e.preventDefault();

    let id = this.refs.id.value;
    let pass = this.refs.pass.value;

    if (!id) {
      this.setState({ message: 'idは必須です。'});
    } else if (!pass) {
      this.setState({ message: 'パスワードは必須です。'});
    } else {
      this.setState({ message: null });
      // api 呼び出し
    }
  }

  render() {
    return (
      this.state.sessionId? (
        <Redirect to={'/'} />
      ) : (
        <div>
          <div>
            <p>{this.state.message}</p>
          </div>
          <div>
            <h1>ログイン</h1>
            <div>
              <input type="text" ref="id" placeholder="email" />
            </div>
            <div>
              <input type="password" ref="pass" placeholder="password" />
            </div>
            <button type="button" onClick={this.onLogin.bind(this)}>ログイン</button>
          </div>
        </div>
      )
    );
  }
}

const mapStateToProps = state => ({
  sessionId: state.loginReducer.login.sessionId,
  message: state.loginReducer.login.message,
});

export default withRouter(connect(mapStateToProps)(Login));

ログイン画面です。
ボタンを押下したときに認証用のapiを叩いています。
今回の記事の趣旨と異なるのでactionとreducerの部分は割愛しますが、認証用のapiを叩いて成功したらsessionIdを、失敗したら失敗理由をstateに詰めてるだけです。
sessionIdがあれば成功とみなし、/にリダイレクトするようにしています。
アクセスされたときのURLをAuth.jsxでどこかに保存しといて成功していたら保存してあるURLにリダイレクトするようにするといいと思います。
exportの箇所でwithRouterconnectをラップしています。
これを使うとrouterのコンテキストを使うようにすることができるみたいです。
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md

Sample.jsx
const Sample = () => (
  <div>
    <p>ログインしたよー</p>
  </div>
)

export default withRouter(Sample);

ログイン後の画面です。

これでログイン処理ができるようになりました。

所感

まだ全然理解していない部分も多いですが、react-router v4でけっこう細かいことまでできそうです。
間違っている部分があれば指摘していただけると助かります!

参考にしたサイト

https://reacttraining.com/react-router/web/guides/quick-start
http://qiita.com/inuscript/items/f28ea779b82adfb133a3
http://qiita.com/suin/items/b7275ff3eb3486380c7e
http://kimagureneet.hatenablog.com/entry/2016/06/08/020458
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md