I just tried to upgrade my React app to
react-router - 4.0.19 to 4.0.20
react- 16.0.30 to 16.0.34
typescript- version "2.7.0-insiders.20180108"
In my app, wherever I am using 'withRouter', I now get cryptic Typescript errors. I even replaced all interface props with 'any' just to try to make it work.
import * as React from 'react';
import { Switch, Route, withRouter} from 'react-router-dom';
import { Login } from './Login';
import { connect } from 'react-redux';
import { RootAction, RootState } from './_redux';
class MainForm extends React.Component<any> {
constructor(props: any) {
super(props);
}
render() {
return (
<Switch>
<Route exact={true} path="/" component={Login}/>
<Route path="/accounts" component={AccountsView}/>
</Switch>
);
}
}
const mapStateToProps = (state: RootState) => ({
state
});
export const Main = withRouter(connect(mapStateToProps)(MainForm);
error TS2345: Argument of type 'ComponentClass> & { WrappedComponent: ComponentType; }' is not assignable to parameter of type 'ComponentType>'. Type 'ComponentClass> & { WrappedComponent: ComponentType; }' is not assignable to type 'StatelessComponent>'. Type 'ComponentClass> & { WrappedComponent: ComponentType; }' provides no match for the signature '(props: RouteComponentProps & { children?: ReactNode; }, context?: any): ReactElement | null'.
If i convert the last line to this :
export const Main = connect(mapStateToProps)(MainForm);
I don't get errors. seriously frustrated here. Thanks
EDIT, I changed to
export const Main = connect(mapStateToProps)(withRouter(MainForm));
like suggested by Mayank Shukla. but now get the error:
error TS2345: Argument of type 'ComponentClass>' is not assignable to parameter of type 'ComponentType<{ state: RootState; } & DispatchProp>'. Type 'ComponentClass>' is not assignable to type 'StatelessComponent<{ state: RootState; } & DispatchProp>'. Type 'ComponentClass>' provides no match for the signature '(props: { state: RootState; } & DispatchProp & { children?: ReactNode; }, context?: any): ReactElement | null'.
connect(mapStateToProps)(withRouter(MainForm))
withRouter(connect(mapStateToProps)(MainForm))
, you need an extra closing parenthesis at the end
I just upgraded to TypeScript 2.6 and got same issue.
I managed to resolve it by using RouteComponentProps
.
For URL http://localhost:8080/your-component/abc
and route
<Route component={YourComponent} path="/your-component/:param1?" />
Component should look like this:
import * as React from 'react'
import { withRouter } from 'react-router-dom';
import {RouteComponentProps} from "react-router";
// Type whatever you expect in 'this.props.match.params.*'
type PathParamsType = {
param1: string,
}
// Your component own properties
type PropsType = RouteComponentProps<PathParamsType> & {
someString: string,
}
class YourComponent extends React.Component<PropsType> {
render() {
console.log(this.props); // Prints all props including routing-related
console.log(this.props.match.params.param1); // Prints 'abc'
console.log(typeof this.props.match.params.param1 === 'string'); // prints 'true'
return <div>...</div>;
}
}
export default withRouter(YourComponent);
I have to solve it like this:
import * as React from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface IProps extends RouteComponentProps<any> {
title: string;
}
class MyComp extends React.Component<IProps> {
public render(){
return (
<h1>{this.props.title}</h1>
)
}
}
export default withRouter<IProps>(MyComp);
withRouter(MyComp)
. Due to a previous upgrade, I needed to add the type argument, however since 5.x it seems to work by leaving it out. "react-router": "5.2.0"
"@types/react-router": "5.1.8"
withRouter
call will result in an error when specifying "unknown" properties on the MyComp
component (i.e. property "title" will not be recognized as valid on <MyComp>
). To specify both, supply withRouter<IProps, React.Component<IProps>>(({staticContext, ...props}) => MyComp(props));
Here's a functional react approach I use
import { RouteComponentProps } from "react-router";
interface Props extends RouteComponentProps {
thing: Thing | false;
onAction?: () => void;
}
export default withRouter(({ thing, onAction, history }: Props) => {
Here is how I usually strucutre my typed React components:
// These props are provided when creating the component
interface OwnProps {
// ...
}
// These props are provided via connecting the component to the store
interface StateProps {
// ...
}
// These props are provided by the router
interface PathProps {
// ...
}
class Component extends React.Component<OwnProps & StateProps & RouteComponentProps<PathProps>> {
// ...
}
const mapStateToProps = (state: State, props: OwnProps): StateProps => ({
// ...
});
export default withRouter(
connect(mapStateToProps)(Component)
);
Another solution, using decorators
import { withRouter, RouteComponentProps } from "react-router";
// inform we match url /:id
interface IMatchParams {
id: string;
}
// Note we use Partial<RouteComponentProps> to make all RouteComponentProps as optional for high order component
interface IComponentProps extends Partial<RouteComponentProps<IMatchParams>> {
myPersonalProp: string;
}
@withRouter
export default class MyClass extends React.Component<IComponentProps>{
public componentDidMount(){
console.log(this.props.match.params.id);
}
}
tsconfig.json
under compilerOptions
the flag experimentalDecorators: true
. If you still have issues, change the compile target
to es5, if the target is es7 it won't transpile the decorator and most browser \ node versions doesn't support it yet.
Working syntax variant for Type Script application is:
import * as React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
interface ComponentProps {
// Your properties here
}
interface ComponentState {
// Your properties here
}
interface MapStateToPropsTypes {
// Your properties here
}
interface MapDispatchToPropsTypes {
// Your properties here
}
class MyComponentName extends React.Component<ComponentProps, ComponentState> {
constructor(props: ComponentProps) {
super(props);
}
}
export default withRouter(
connect<MapStateToPropsTypes, MapDispatchToPropsTypes>(
mapStateToProps,
mapDispatchToProps
)(MyComponentName) as any
);
I was struggling with a very similar/same issue with Typescript 3.6 and couldn't find a solution online so I'll share my own solution here. I hope it helps someone working with a more complex app.
import React, { memo } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { ThunkDispatch } from 'redux-thunk';
import { connect } from 'react-redux';
import { AnyAction } from 'redux';
interface IStateProps {
name: string;
sessionLanguage: string;
}
interface IDispatchProps {
handleLogout: () => void;
}
type Props = IStateProps & IDispatchProps & RouteComponentProps<any>;
const MyCoolComponent = ({
sessionLanguage,
handleLogout,
history,
}: Props) => {
return null;
};
const mapStateToProps = (state: IAppState): IStateProps => ({
name: state.getIn(['session', 'name']),
sessionLanguage: state.getIn(['session', 'language']),
});
const mapDispatchToProps = (
dispatch: ThunkDispatch<{}, {}, AnyAction>
): IDispatchProps => ({
handleLogout: async () => {
await dispatch(logout());
},
});
export default withRouter(
connect<IStateProps, IDispatchProps, {}, IAppState>(
mapStateToProps,
mapDispatchToProps
)(memo(NavigationLayout))
);
Some notes:
Important parts are the interfaces, RouteComponentProps, type Props, React component typing and the export default withRouter(...). mapStateToProps and mapDispatchToProps are just examples.
IAppState defines my app's redux store's typings. If you don't have it.
I'm using immutable redux store here (that's why "state.getIn...").
I have come across this issue and the closest answer to my problem was this thread. However, I had to slightly change the suggestions to below. Sharing if in case helps anyone else...
import { RouteComponentProps, withRouter } from 'react-router';
import * as React from 'react';
export interface MyComponentProps extends RouteComponentProps<any> {
propA: String;
propB: Number;
}
function MyComponent(props: MyComponentProps) {
return (
<div>
<div>{props.propA} - {props.propB}</div>
<button onClick={() => props.history.push('/some-other-page')}>Go</button>
</div>
)
}
export default withRouter(MyComponent);
In order to still permit custom parameters to be used on your component, you must supply your props interface to withRouter
.
In addition, withRouter
requires that you specify the type of component in use (i.e. FunctionComponent / Component).
Also note that withRouter
will supply staticContext
along with the props. This should be removed from the set of. props before passing them along to the wrapped component, otherwise, you'll get this error (unless you specifically interfaced your component to accept staticContext
).
index.js:1 Warning: React does not recognize the 'staticContext' prop on a DOM element...
For a Function Component, here is an example of how to properly type the withRouter
wrapper:
For a Class Component, here is an example of how to properly type the withRouter
wrapper.
import React, { FunctionComponent } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface MyCompProps extends RouteComponentProps<any> {
title: string;
}
const MyComp: FunctionComponent<MyCompProps> = ({ title }) => (
<h1>{ title }</h1>
);
export default withRouter<MyCompProps, Component<MyCompProps>>(({ staticContext, ...props }) => MyComp(props));
For a Class Component, here is an example of how to properly type the withRouter
wrapper.
import React, { Component } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface MyCompProps extends RouteComponentProps<any> {
title: string;
}
class MyComp extends Component<MyCompProps> {
public render(){
return (
<h1>{this.props.title}</h1>
)
}
}
export default withRouter<MyCompProps, Component<MyCompProps>>(({ staticContext, ...props }) => MyComp(props));
The two only keys for me are:
Type the props correctly
interface MyComponentProps extends RouteComponentProps {/*...*/}
class MyComponent extends React.Component<MyComponentProps , MyComponentState> {/*...*/}
withRouter() wraps connect()
withRouter(
connect(null, {
...MyReduxActions
})(MyComponent)
);
if there is a problem with type "any" you can do such a trick. It worked for me.
import { withRouter, RouteComponentProps } from 'react-router-dom';
type ComponentProps = RouteComponentProps;
const Component: React.FC = () => {
return <div>This is component</div>
}
export default withRouter(Component)
Success story sharing