綺麗に死ぬITエンジニア

react-router-redux 5.xで正常にロケーションを反映できない場合の対処法

2018-01-15

react-router-redux 5.x」をサンプルの通りインストールすると、場合によってはブラウザの戻る/進む操作によって画面の状態がおかしくなることがあります。

2018年1月現在、react-router-redux 5.xの最終バージョンがv5.0.0-alpha.9なので、まだドキュメントが整備されていないだけかもしれませんが、詳しい情報の記載がないため、どう修正すればいいかわからない方も多いかと思いますので、備忘録を兼ね紹介しておきます。

現象

react-router-redux 5.xをインストールした環境において、ブラウザの戻るボタンを連打すると、画面の状態が戻ったり戻らなかったりし、更に進むボタンを押すと、更に進んだり進まなかったりし、結果的に画面の各コンポーネントにおける状態がズレたりします。

例えば、特定の画面を開いたときアクティブな<NavLink/>に特定のスタイルを当てていたとして、戻る/進むボタンによって、実際に表示されている画面とアクティブな<NavLink/>とにズレが生じたりします。

実際はトップページを開いているのに、<NavLink/>は別のページを開いていることになっていたり。

react-router-redux 5.xのインストール手順

2018年1月現在、下記の手順でインストールしたものが5.xのものとなります。それ以前のバージョンは、本記事での解説の対象外です。

npm install --save react-router-redux@next
npm install --save history

現象が発生するコード

GitHubのREADME.mdに記載のサンプルコードで、該当の現象が発生します。

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import createHistory from 'history/createBrowserHistory'
import { Route } from 'react-router'
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'
import reducers from './reducers'

const history = createHistory()
const middleware = routerMiddleware(history)
const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
)

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <div>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
        <Route path="/topics" component={Topics}/>
      </div>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
)

対処法

<Route/>コンポーネントの親要素として、プロパティlocationにreact-router-reduxによって生成されたlocationオブジェクトを割り当てた、<Switch/>コンポーネントを定義します。

以下のサンプルでは、その<Switch/>コンポーネントを<ConnectedSwitch/>として定義しています。

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import createHistory from 'history/createBrowserHistory'
import { Route, Switch } from 'react-router'
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'
import reducers from './reducers'

const history = createHistory()
const middleware = routerMiddleware(history)
const store = createStore(
  combineReducers({
    ...reducers,
    router: routerReducer
  }),
  applyMiddleware(middleware)
)

const ConnectedSwitch = connect(state => ({
  location: state.router.location
}))(Switch)

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <ConnectedSwitch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
        <Route path="/topics" component={Topics}/>
      </ConnectedSwitch>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
)

また、<NavLink/>を利用している場合は、<NavLink/>に対しても同様に、プロパティlocationにreact-router-reduxによって生成されたlocationオブジェクトを割り当てる必要があります。

import { connect } from 'react-redux'
import { NavLink } from 'react-router-dom'

export const ConnectedNavLink = connect(state => ({
  location: state.router.location
}))(NavLink)

こうすることで、問題は解決するかと思います。

まとめ

react-router-redux 5.xからreact-routerパッケージに含まれるようになり、本バージョンはreact-router 4.xと互換性を持ちます。

JavaScriptライブラリはどれも他の言語のライブラリに比べ変更が多く、バージョンアップ等の対応に悩まされることも多いかと思いますが、少しでもこの記事がそういった方々の助けになれば幸いです。