ChatGPT解决这个技术问题 Extra ChatGPT

React Router - Typescript errors on withRouter after updating version

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

try this: connect(mapStateToProps)(withRouter(MainForm))
thanks @MayankShukla. i think it's progress but now I get the error , as explained above.
I guess your syntax is incorrect, withRouter(connect(mapStateToProps)(MainForm)), you need an extra closing parenthesis at the end
i fixed that but still got the same error :(

P
Pavel

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

good to know! I actually just removed withRouther entirely in the app and went with using BrowserRouter. import { BrowserRouter as Router, Route, RouteComponentProps } from 'react-router-dom'; For some reason doing this allowed me to get rid of it and still pass routing props.
I used withRouter for App.tsx.
j
jakobdo

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

@jakbdo I get an error --- expected 2 type arguments but got 1, on withRouter.
@DeltaTango had the same issue after upgrading to 5.x. I Solved it by removing the type argument entirely, this suddenly worked: 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"
@MarcelKirsche removing the type declaration from the 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));
o
oliyoung

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

M
Max

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

D
Daniel Krom

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

Thanks Daniel. good stuff, do you mind me asking what other libs you needed to bring into your build to support decorators ? I tried not too long ago and just ran into too many TS errors...so gave up
@29er The first step you need to do it adding at the 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.
J
Jackkobec

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 love using TypeScript in my project, but I give everything a type of any and it works!!"
Right you are. But this only example about component connect syntax, not about typescript.
R
Raunhofer

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


M
Mahmut C

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



W
WebWanderer

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

P
Pablo LION

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

T
Thurabli

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)