LoginSignup
9
6

More than 5 years have passed since last update.

[React Router] ページ遷移が発生するよりも前に Redux のステートを更新したい

Last updated at Posted at 2018-07-26

問題

react-router-redux@@router/LOCATION_CHANGE イベントはページ遷移のに実行される。

sagas.js
import { LOCATION_CHANGE } from 'react-router-redux'

export default function *rootSaga() {
  while (true) {
    const action = yield take(LOCATION_CHANGE)
    // update current user using route parameter
  }
}
App.js
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'
import Routes from './Routes'
import configure from './redux/store'

const { store, history } = configure()

store.runSaga()

const App = () => (
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <Routes />
    </ConnectedRouter>
  </Provider>
)

export default App
Routes.js
import React from 'react'
import Timeline from './Timeline'

const Routes = (props) => (
  <Switch>
    <Route component={Timeline} exact path="/timeline" />
    {// ...}
  </Switch>
)

export default Routes
Timeline.js
import React, { Component } from 'react'

export default class Timeline extends Component {

  componentDidMount() {
    this.props.fetchTimelineOfCurrentUser()
  }

  componentDidUpdate() {
    //
  }

  render() {
    //
  }

}

ここでもし fetchTimelineOfCurrentUser() がReduxのステートに依存しているとすると,前のルートに基づくステートをパラメータとして使ってデータフェッチが走ってしまうことになる。

新しいルートに基づいてReduxのステートを再構成した上で,それを使ってcomponentDidMount()を実行させたい。どうすればいいか?

解決策

ステート更新に非同期処理が入ってくる場合は未解決だが,同期処理のみで済む場合はスマートな解決策がある。

rootSaga の処理は消した上で StoreUpdater というコンポーネントを作る。 render メソッドの返り値は常に null で問題ない。

StoreUpdater.js
import React, { Component } from 'react'

export default class StoreUpdater extends Component {

  componentDidMount() {
    // do something synchronous store update here
  }

  componentDidUpdate(prevProps, prevState) {
    // do something synchronous store update here
  }

  render() {
    return null
  }

}

このコンポーネントを Routes の中に仕込む。ConnectedRouter は単一のノードしか受け入れないので Fragment でラップする。

Routes.js
import React, { Fragment } from 'react'
import StoreUpdater from './StoreUpdater'
import Timeline from './Timeline'

const Routes = (props) => (
  <Switch>
    <Route component={Timeline} exact path="/timeline" />
    ...
  </Switch>
)

const FreshRoutes = (props) => (
  <Fragment>
    <StoreUpdater {...props} />
    <Routes {...props} />
  </Fragment>
)

export default Routes

これだけ。 StoreUpdater のライフサイクルメソッドは必ず Routes より先に実行されるので,同期処理のみの場合はこれで解決できる。いちいち全ページコンポーネントの componentDidMount() componentDidUpdate() に更新処理を書いたりする必要はない。

非同期処理の場合は…どうするんでしょうね?

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6