はじめに
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型を指定する