Definitelytyped: @types/react-router : withRouter with react-redux connect

Created on 16 Aug 2017  路  14Comments  路  Source: DefinitelyTyped/DefinitelyTyped

  • [x] I tried using the @types/react-router package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript (2.4.2)
  • [x] 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: @RyanCavanaugh @tkrotoff

Currently, withRouter is having this definition:
export function withRouter<P>(component: React.ComponentType<RouteComponentProps<any> & P>): React.ComponentClass<P>;

However, when using with connect of react-redux like mentioned in the documentation the parameter doesn't match. Here is the exception:

Argument of type 'ComponentClass<{}>' is not assignable to parameter of type 'ComponentType<RouteComponentProps<any>>'.
  Type 'ComponentClass<{}>' is not assignable to type 'StatelessComponent<RouteComponentProps<any>>'.
    Type 'ComponentClass<{}>' provides no match for the signature '(props: RouteComponentProps<any> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

This happen using exactly:
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PageContent));

A workaround is to cast with any:

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PageContent) as any)

connect doesn't return React.ComponentType<RouteComponentProps<any> & P> but React.ComponentClass<{}>

Most helpful comment

I could avoid any by using RouteComponentProps which @NicholasBoll said above though I don't completely understand this mechanism.

interface MyProps extends RouteComponentProps<MyProps> {}

class TestComponent extends React.Component<MyProps, {}> {}

const mapStateToProps = () => ({});

const mapDispatchToProps = () => ({});

const Test = withRouter(connect(mapStateToProps, mapDispatchToProps)(TestComponent));

All 14 comments

The same problem too, but thanks for the workaround !

Thank you for logging this, I ended up reverting to 3.0.12 (at least until this gets fixed).

@MrDesjardins Is this still a problem? I can make a test case in the tests with the following and I don't get that error. I get a different error about <Test /> needing all the properties listed in RouterComponentProps (location, match, history).

namespace WithRouter {
    class TestComponent extends Component<RouteComponentProps<any>> {}

    const mapStateToProps = () => ({});

    const mapDispatchToProps = () => ({});

    const Test = withRouter(connect(mapStateToProps, mapDispatchToProps)(TestComponent)); // this is fine

    const verify = <Test />; // requires `location`, `match` and `history`. I don't know if that is expected
}

@NicholasBoll I still need to do the workaround I proposed to move forward with the library but I haven't updated anything since. Here is a screenshot of the actual problem.

image

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>

This seems to extend to the Route component as well, when I try to do

const authenticatedRedir = connectedRouterRedirect({...})
<Route path="/" component={authenticatedRedir(connectedApp)} />

I'm getting a similar Argument of type 'ComponentClass<...>' is not assignable to parameter of type 'ComponentType<...>' which is also fixed by casting to any. Thanks for the workaround!

I could avoid any by using RouteComponentProps which @NicholasBoll said above though I don't completely understand this mechanism.

interface MyProps extends RouteComponentProps<MyProps> {}

class TestComponent extends React.Component<MyProps, {}> {}

const mapStateToProps = () => ({});

const mapDispatchToProps = () => ({});

const Test = withRouter(connect(mapStateToProps, mapDispatchToProps)(TestComponent));

Issues very similar to mine:
https://stackoverflow.com/questions/48260971/react-ts-withrouter-connect-error
https://stackoverflow.com/questions/48219432/react-router-typescript-errors-on-withrouter-after-updating-version
Issues somehow related to mine, as far as I can tell:
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/21329
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355

package-lock.json:

"@types/react-router": {
      "version": "4.0.22",

I tried many different types for the Component props, like the one in the post above this one. None of that worked.
Component code:

class HeaderTabs extends React.Component<RouteComponentProps<any>, any> {}
const mapStateToProps = () => ({})
const mapDispatchToProps = () => ({})
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HeaderTabs));

The error I have is:

Argument of type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' is not assignable to parameter of type 'ComponentType<RouteComponentProps<any>>'.
  Type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' is not assignable to type 'StatelessComponent<RouteComponentProps<any>>'.
    Type 'ComponentClass<Pick<any, never>> & { WrappedComponent: ComponentType<any>; }' provides no match for the signature '(props: RouteComponentProps<any> & { children?: ReactNode; }, context?: any): ReactElement<any> | null'.

The only workaround was to use:

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HeaderTabs) as any);

Please advice

Just a heads up - you shouldn't be passing in component props as the to RouteComponentProps. The _type variable_ should describe your intended params:

For example, if you expect two params (blogName and blogId) and a few props (logo and items) on this Route, then your full component props would look like:

interface IProps extends RouteComponentProps<{ blogName: string, blogId: number  }> {
  className?: string
  logo?: React.ReactNode
  items?: Array<INavItem>
}

Note in the following image how VSCode is correctly picking up my params:

image

After some investigation. I found a simple solution. What you only have to do is to have "compilerOptions": { "strict": true } in your tsconfig.json if you're connecting a React.Component<,> component.

Thanks for this workaround @MrDesjardins ! Got badly stuck with router/redux with typescript integration and this saved me some hours.

@guilhermehubner and @emag's posts gave me an idea on how the withRouter mechanism works.

I find that you don't even have to extend the RouteComponentProps, if you pass a compatible props to the withRouter, it will automatically extends the RouteComponentProps.

Here's how I got it to work:

In the Component:

type Props = {
  router: any; // Make sure you include router props here
  createZone?: any;
  onInitialized?: any;
};

type State = {
  message: string;
  loading: boolean;
};

class InitializeScreen extends React.PureComponent<Props, State> {
// Component stuff here
}

// Finally 

export default withRouter<Props>(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(InitializeScreen)
);

To use the component

//Somewhere in your code:
<InitializeScreen onInitialized={onInitialized} />

Here's an example of how to use together within a functional component

interface MyProps extends RouteComponentProps {
  otherProp: string;
}

const UnconnectedPrivateRoute = ({
  otherProp,
  history
}: MyProps) => (
    .........
)
const mapStateToProps = (state: RootState) => ({
  username: state.user.attributes?.userName,
  attributes: state.user.attributes,
  isLoggedIn: state.user.isLoggedIn
});

const mapDispatchToProps = { logout };

export const PrivateRoute = withRouter(
  connect(mapStateToProps, mapDispatchToProps)(UnconnectedPrivateRoute)
);


Was this page helpful?
0 / 5 - 0 ratings