React Router - Typescript errors on withRouter after updating version

ReactjsTypescriptReact Router

Reactjs Problem Overview


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'.

Reactjs Solutions


Solution 1 - Reactjs

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);

Solution 2 - Reactjs

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);

Solution 3 - Reactjs

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) => {

Solution 4 - Reactjs

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)
);

Solution 5 - Reactjs

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);
    }
}

Solution 6 - Reactjs

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
    );

Solution 7 - Reactjs

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...").

Solution 8 - Reactjs

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);


Solution 9 - Reactjs

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));

Solution 10 - Reactjs

The two only keys for me are:

  1. Type the props correctly
interface MyComponentProps extends RouteComponentProps {/*...*/}
class MyComponent extends React.Component<MyComponentProps , MyComponentState> {/*...*/}
  1. withRouter() wraps connect()
withRouter(
  connect(null, {
    ...MyReduxActions
  })(MyComponent)
);

Solution 11 - Reactjs

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Question29erView Question on Stackoverflow
Solution 1 - ReactjsPavelView Answer on Stackoverflow
Solution 2 - ReactjsjakobdoView Answer on Stackoverflow
Solution 3 - ReactjsoliyoungView Answer on Stackoverflow
Solution 4 - ReactjsMaxView Answer on Stackoverflow
Solution 5 - ReactjsDaniel KromView Answer on Stackoverflow
Solution 6 - ReactjsJackkobecView Answer on Stackoverflow
Solution 7 - ReactjsRaunhoferView Answer on Stackoverflow
Solution 8 - ReactjsMahmut CView Answer on Stackoverflow
Solution 9 - ReactjsWebWandererView Answer on Stackoverflow
Solution 10 - ReactjsPablionView Answer on Stackoverflow
Solution 11 - ReactjsThurabliView Answer on Stackoverflow