JavaScript
TypeScript
react-router
redux

Redux + React Router + TypeScript環境 connectで生成したHOCでLink遷移ができない

はじめに

Redux + React Router + TypeScriptの環境を構築していたところ
connectで生成されたコンポーネント内でReact RouterのLinkでうまく遷移ができななかったので、調べた解決方法を載せておきます。

環境

開発要素 バージョン
TypeScript 2.4.1
react 15.6.1
react-router-dom 4.1.1
redux 3.7.2
react-redux 5.0.5

問題

React RouterのLinkコンポーネントを使用したリンクをクリックしても遷移しない(renderメソッドが実行されない)。

その他調べて分かったこと
  • reduxなし(Providerやconnectなし)の場合はリンクで遷移する
  • Linkをクリック時は遷移しないが、そのあとに dispatch呼び出す動作をすると 遷移する

ソース

問題が発生した時のソースコード(一部)

Root.tsx
import * as React from "react"
import { BrowserRouter } from "react-router-dom"
import { Provider } from "react-redux"
import App from "./App"

const store = // ... ストア生成

class Root extends React.Component<{}, {}> {
  public render() {
    return (
      <Provider store={store}>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Provider >
    )
  }
}

export default Root
App.tsx
import * as React from "react"
import { connect } from "react-redux"
import { Switch, Route, Link } from "react-router-dom"

interface Props extends React.Props<{}> {
  /* ... */
}

class App extends React.Component<Props, {}> {
  public render() {
    return (
      <Switch>
        // <Link />コンポーネントはChildA, ChildBの中で指定
        <Route exact={true} path="/" component={ChildA} />
        <Route path="/page2" component={ChildB} />
      </Switch>
    )
  }
}

/* RootStateはアプリのStateの型 */
const mapStateToProps = (rootState: RootState, onwProps: Props) => ({
  /* ... */
})

const mapPropsToDispatch = {
 /* ... */
}

export default connect(mapStateToProps, mapPropsToDispatch)(App)

RootコンポーネントではProvider, BrowserRootでAppコンポーネント(アプリケーションのメインコンポーネント) ラップして呼び出している。

対応1

React Routerドキュメントによると、connectで生成されたコンポーネントの場合、Linkで遷移ができないことが記載されていた。
これの対応方法としてwithRouterメソッドでラップしなければならいよう。

--- import { Switch, Route, Link } from "react-router-dom"
+++ import { Switch, Route, Link, withRouter } from 'react-router-dom

--- export default connect(mapStateToProps, mapPropsToDispatch)(App)
+++ export default withRouter(connect(mapStateToProps, mapPropsToDispatch)(App))

別の問題が発生

上記のようにコードを修正したところ、今度はTypeScriptのビルドエラーが発生。

ERROR in [at-loader] ./src/pages/App.tsx:57:31
    TS2345: Argument of type 'ComponentClass<{}>' is not assignable to parameter of type 'StatelessComponent<RouteComponentProps<any>> | ComponentClass<RouteComponentProps<any>>'.
  Type 'ComponentClass<{}>' is not assignable to type 'ComponentClass<RouteComponentProps<any>>'.
    Type '{}' is not assignable to type 'RouteComponentProps<any>'.
      Property 'match' is missing in type '{}'.

対応2

withRouterの引数はコンポーネントのPropsにRouteComponentPropsを指定したコンポーネントである必要がある。
そこで、AppコンポーネントのPropsにRouteComponentPropsを継承した型を使用するよう修正した。

--- import { withRouter } from 'react-router-dom'
+++ import { withRouter, RouteComponentProps } from 'react-router-dom'

--- interface Props extends React.Props<{}> {
+++ interface Props extends RouteComponentProps<{}>, React.Props<{}> {

--- export default withRouter(connect(mapStateToProps, mapPropsToDispatch)(App))
+++ export default withRouter<{}>(connect(mapStateToProps, mapPropsToDispatch)(App))

以上で、Linkでの遷移ができるようになった。

まとめ

  • コンポーネントのPropsにRouteComponentPropsのプロパティが必要
  • connectで生成されるコンポーネントの型に元コンポーネントのPropsを引き継ぐにはmapStateToPropsの第二引数に元コンポーネントのProps型を指定する

参考