Definitelytyped: [@types/react-router-dom]: withRouter HoC produces compile error: "Property 'match' is missing in type '{}'."

Created on 14 Jun 2017  ·  24Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/xxxx package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [ ] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @sergey-buturlakin, @mrk21, @vasek17 and many more, should I really mention them all?

I did a Google search and found two other people with the same problem, but no solution so far:
https://stackoverflow.com/questions/44118060/react-router-dom-with-typescript
https://github.com/Microsoft/vscode/issues/27235

Minimal code example:

````javascript
const MyRouterComponent = withRouter(
class MyComponent extends React.Component, any> {
render() {
return

;
}
}
)

class MainComponent extends React.Component {
render() {
/* This line produces following compile error:
error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes, ComponentState...'.
Type '{}' is not assignable to type 'Readonly>'.
Property 'match' is missing in type '{}'.
*/
return
}
}
````

I understand what the problem is: MyRouterComponent expects props which I didn't explicitly pass, because withRouter will take care of that. So how does one use withRouter with TS correctly?

Most helpful comment

Same issue here:

import React from 'react'
import { NavLink } from 'react-router-dom'
import { withRouter, RouteComponentProps } from 'react-router'
import { Project } from 'state'

interface Props {
  className: string
}

class SideBar extends React.Component<Props & RouteComponentProps<any>> {
  render() {
    const { className, match: { params: { project } } } = this.props
    return (
       <h1>{project}</h1>
    )
  }
}

export default withRouter(SideBar)
class Project extends React.Component<{}> {
  render() {
    return (
        <SideBar className="project__sidebar"/>
    )
  }
}

Got:

./src/components/Project.tsx
(14,18): error TS2322: Type '{ className: "project__sidebar"; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<SideBar> & Readonly<{ children?: ReactNode; }> & R...'.
  Type '{ className: "project__sidebar"; }' is not assignable to type 'Readonly<Props & RouteComponentProps<any>>'.
    Property 'match' is missing in type '{ className: "project__sidebar"; }'.

Can be fixed with:

export default withRouter<Props>(SideBar)

Bug with withRouter declaration, it can't extract real props.

All 24 comments

I'm having the same issue after upgrading react and related modules versions, following

@hafuta, are you suggesting that this has worked in previous versions? I'm trying to use withRouter for the first time, so I don't know. I'm using the latest versions of everything.

As a temporary workaround I figured out this:

javascript return <MyRouterComponent { ...({} as RouteComponentProps<any>) } />

TS compiler is happy and withRouter injects the correct router props afterwards.

@simonvizzini, yes, it worked for me before upgrading some packages. I'm using more or less the same workaround, I'm just passing nulls, the withRouter function does inject the right values.

<DashboardMenu match={null} location={null} history={null} />

@simonvizzini You need to do something like:

const MyRouterComponent = withRouter<{}>(
  class MyComponent extends React.Component<RouteComponentProps<{}>, any> {
    render() {
      return <div />;
    }
  }
)

class MainComponent extends React.Component<any, any> {
  render() {
    return <MyRouterComponent />
  }
}

Since before, withRouter typing was:
export function withRouter(component: React.SFC<RouteComponentProps<any>> | React.ComponentClass<RouteComponentProps<any>>): React.ComponentClass<any>;

and now is:
export function withRouter<P>(component: React.SFC<RouteComponentProps<any> & P> | React.ComponentClass<RouteComponentProps<any> & P>): React.ComponentClass<P>;

@AlgusDark thank you very much! This indeed works. I'm now having a different problem, but I think I'm just doing it wrong. For example, this produces a compiler error:

````js
interface Props {
propA: string;
propB: number;
}

const MyRouterComponent = withRouter(
class MyComponent extends React.Component, any> {
render() {
return

;
}
}
)

class MainComponent extends React.Component {
render() {
return
}
}
````

Instead I have to do something like this:

js class MyComponent extends React.Component<RouteComponentProps<{}> & Props, any> {

Looking at the type definition, it seems obvious why the first approach doesn't work, but is the second approach really the correct way? It doesn't seem to be very intuitive to me, but then again, I'm still learning TypeScripts type system so I'm probably just missing something.

@simonvizzini I'm too confused with this new way, because I feel that we can write this in a better way.

Maybe @mhegazy can help us with this, he did the changes at this commit.

Edit: Btw, Props in RouteComponentProps<Props> are the match.params. So RouteComponentProps<{}> & Props should be the correct way.

I've found a way to solve this problem as you can see below:

type OwnProps = RouteComponentProps<{id: number}>;

const mapStateToProps = (state: GlobalStateType, ownProps: OwnProps): StateToPropsType => ({
        id: ownProps.match.params.id
});

type PropsType = StateToPropsType & DispathToPropsType & OwnProps;

class MyPage extends React.Component<PropsType, StateType> {
   ...
}

const mapDispatchToProps = (dispatch: DispatchType): DispathToPropsType => ({
    ...
});

export default withRouter(connect<StateToPropsType, DispathToPropsType, OwnProps> (
    mapStateToProps,
    mapDispatchToProps
)(MyPage));

In router:

<Provider store={store}>
   <BrowserRouter>
      <Switch>
         <Route path="/:id(\d+)" component={MyPage} />
      <Switch>
  <BrowserRouter>
</Provider>

React Redux typings use a neat trick with Omit mapped type. Maybe this pattern can be used here as well?

I wrote this module augmentation:

react-router-dom.d.ts

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

declare module 'react-router-dom' {
  // Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766
  type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
  type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>

  export function withRouter<P extends RouteComponentProps<any>>(
    component: React.ComponentType<P>
  ): React.ComponentClass<Omit<P, keyof RouteComponentProps<any>>>
}

It works for me. What do you think?

Same issue here:

import React from 'react'
import { NavLink } from 'react-router-dom'
import { withRouter, RouteComponentProps } from 'react-router'
import { Project } from 'state'

interface Props {
  className: string
}

class SideBar extends React.Component<Props & RouteComponentProps<any>> {
  render() {
    const { className, match: { params: { project } } } = this.props
    return (
       <h1>{project}</h1>
    )
  }
}

export default withRouter(SideBar)
class Project extends React.Component<{}> {
  render() {
    return (
        <SideBar className="project__sidebar"/>
    )
  }
}

Got:

./src/components/Project.tsx
(14,18): error TS2322: Type '{ className: "project__sidebar"; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<SideBar> & Readonly<{ children?: ReactNode; }> & R...'.
  Type '{ className: "project__sidebar"; }' is not assignable to type 'Readonly<Props & RouteComponentProps<any>>'.
    Property 'match' is missing in type '{ className: "project__sidebar"; }'.

Can be fixed with:

export default withRouter<Props>(SideBar)

Bug with withRouter declaration, it can't extract real props.

Use my module augmentation to extract props correctly.
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17181#issuecomment-339401310
We should do a PR with these modifications.

I will try to propose a PR today.

@Grmiade Any progress on this?

Sorry, I was overwhelmed these days, I will try this week

This PR https://github.com/DefinitelyTyped/DefinitelyTyped/pull/21329, in particular this line looks to have changed the meaning of the generic P in withRouter<P>. Previously, it meant the "own" properties expected by the Component returned by withRouter; now, it means to also include the properties injected by withRouter e.g. match. Is my understanding of this change correct? Is this the intended API?

This change broke a large swath of our application as we depended on it meaning "own" properties. I'm trying to figure out if I should update the application to reflect this new meaning or make a new issue.

Thanks!

@jbmilgrom Your understanding is correct 👍
With this changes, it's not longer required to specify your "own" properties. It's inferred 🎉
I think you should update your application by removing your useless return type. You can found an example here.

According to me, specify his "own" properties expected by the component returned is a bad idea. It's too permissive and authorize the cheat :/ withRouter's definition is the only one able to define the real return type. If you see what I mean ;) Thereby your typing becomes safe 💪 What do you think?

I think you're right, thanks for the quick response!

In our use cases, the consumers of the components returned by withRouter care only about "own" props when trying to guarantee their correct use, which is accomplished both with and without this change, but I'm glad the new type now more accurately reflects whats going on.

This example: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-router/test/WithRouter.tsx

is not working for me. The parent component that is trying to use this withRoutered component is being scolded that it hasn't supplied match which is of course an internal prop.

"react-router-dom": "^4.2.2",
"@types/react-router-dom": "^4.2.2",

I get this error on windows only.

Something this simple worked for me:

class BranchesList extends React.Component<
  { branches: BranchesModelType } & RouteComponentProps<{ page: number }>,
  {}
> {
  public render() {
 // render something here
    );
  }
}

export default withRouter(BranchesList) as typeof BranchesList;

typeof is always such a life saver.

I have the same problem. but I took another method.
create new file in src/history.js

import createHistory from 'history/createBrowserHistory'  
export default createHistory()
````

and change the index.tsx
````
import { Router } from 'react-router-dom';
ReactDOM.render(
  <BrowserRouter history={history}>
    <App />
  </BrowserRouter>
  , document.getElementById('root') as HTMLElement
);
````
and In the component, I can use the history.push("/path")

import history from '../../untils/history'
class ReviseCenter extends React.Component{
handleHistory () {
history.push('/home')
}
}
```
if I use withRouter it's wrong

Was this page helpful?
0 / 5 - 0 ratings