Definitelytyped: [@types/next] `withRouter` in TypeScript

Created on 5 Mar 2018  路  12Comments  路  Source: DefinitelyTyped/DefinitelyTyped

I want to use a decorator to refer to the router, but there is a problem with type checking in TypeScript. (Does not affect its work, only type checking error)

@types/next/router.d.ts

export function withRouter<T extends {}>(
  Component: React.ComponentType<T & { router: SingletonRouter }>
): React.ComponentType<T>;

First of all, I think a little problem in the type definition, I think router property should be optional.
Because router property is injected fromwithRouter does not require passing this property when I refer to the component.

export function withRouter<T extends {}>(
-  Component: React.ComponentType<T & { router: SingletonRouter }>
+  Component: React.ComponentType<T & { router?: SingletonRouter }>
): React.ComponentType<T>;

There is no problem using function injection

import * as React from 'react'
import { withRouter, SingletonRouter } from 'next/router'

interface Props {
  router?: SingletonRouter
}

class SiderMenu extends React.Component<Props> { }
export default withRouter(SiderMenu)

There was an error using the decorator

import * as React from 'react'
import { withRouter, SingletonRouter } from 'next/router'

interface Props {
  router?: SingletonRouter
}

@withRouter
export default class SiderMenu extends React.Component<Props> { }

TS Error

@withRouter
^^^
[ts]
Unable to resolve signature of class decorator when called as an expression.
  Type 'ComponentClass<Props>' is not assignable to type 'void | typeof SiderMenu'.
    Type 'ComponentClass<Props>' is not assignable to type 'typeof SiderMenu'.
      Type 'Component<Props, ComponentState>' is not assignable to type 'SiderMenu'.
        Types of property 'render' are incompatible.
          Type '() => ReactNode' is not assignable to type '() => Element'.
            Type 'ReactNode' is not assignable to type 'Element'.
              Type 'string' is not assignable to type 'Element'.

@dru89 @brikou

Most helpful comment

The just-keep-quiet option:

@(withRouter as any)

All 12 comments

So for the first part about it injecting the value of router, I think the solution then is to actually change the signature to this (which makes more sense anyways):

export function withRouter<T extends {}>(
  Component: React.ComponentType<T>
): React.ComponentType<T & { router: SingletonRouter }>;

This makes more sense because it's a higher-order-component that actually _returns_ a component that has a router as a prop.

This doesn't fix your decorator use-case. But I don't see any documentation about it being used as a decorator anywhere. Do you have an example of that from their docs somewhere?

I've tried a few things and I'm not sure how to make the @withRouter bit resolve like it's supposed to, either. I'm also not even confident about the proposed change above, tbh.

Feel free to submit a pull request or provide additional information. I'll think about this a little tonight and see if something jumps out at me.

@Dru89: But I don't see any documentation about it being used as a decorator anywhere. Do you have an example of that from their docs somewhere?

Yes, the next.js documentation does not say that decorators can be used.
But the decorator is really just a syntactic sugar, actually a function.
Use decorators can be more convenient, especially when using multiple HOC

Hi @u3u! I've created a change in a forked side-branch that I think gets us _close_ to what you're looking for. But I've left a few open TODOs that I think need to get corrected before I would open a pull request against this repo.

Feel free to take a look at them and take a crack at them if you want this to work on decorators:
https://github.com/DefinitelyTyped/DefinitelyTyped/compare/master...Dru89:experimental-types/nextjs-decorator

I'd basically require that the following TODO gets fixed before I'd change this myself, though:
https://github.com/Dru89/DefinitelyTyped/blob/c5d709b4c41331df646d0ad66dd8d40cc4f1603a/types/next/test/next-router-tests.tsx#L101-L104

@r.withRouter
class DecoratedComponent extends React.Component<{ name: string, router: r.SingletonRouter }> {
    render(): React.ReactNode {
        return <div data-router={this.props.router} id={this.props.name} />
    }
}

// TODO: the prop types on the decorated component only work if you pass
// a router along with it (since I can't seem to provide casting to withRouter
// in the same way we can with WrappedComponent)
<DecoratedComponent name="foo" router={Router} />

I think what we're getting blocked on here is Microsoft/TypeScript#4881, since decorators can't modify the returned type of the thing that they're decorating currently.

Thank you, I was busy recently and I forgot to watch this.
Yes, there is one more difficult problem to solve with decorators. If other properties are defined in the decorated class, an error occurs: Property 'xxx' is missing in type 'ComponentClass'.

I'm using this at the moment, which is not very good:

export function withRouter<P>(
    component: React.ComponentType<P>
): React.ComponentClass<WithRouterInferredProps<P>>;

export function withRouter<
    T extends {
        new (props: Partial<RouteComponentProps<any>>, ...args: any[]): any;
    }
>(component: T): T;

Overloading the withRouter so I can get decorators working.

I think anything is better than the "function only" that is currently in the definition. If TS does not allow to typing it correctly, the provided typings should not prevent the use of decorators, most common use case.

However best would be to find a definition that only works for decorators and one that works only for function usage, it would not ruin either way of using the withRouter.

I encountered the same problem, Is there any temp solution for this?

I'm very new to TS, is there anyway to override function type?

from

export function withRouter<T extends {}>(
  Component: React.ComponentType<T & { router: SingletonRouter }>
): React.ComponentType<T>;

to

export function withRouter<T extends {}>(
  Component: React.ComponentType<T & { router?: SingletonRouter }>
): React.ComponentType<T>;

The just-keep-quiet option:

@(withRouter as any)

Hours of googling led me to this solution:

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

interface AnythingProps extends Partial<RouteComponentProps> {
  // your props interface
}

@(withRouter as any)
export class Anything extends Component<AnythingProps> {
  // your class
}

Hope it'll help someone :)

The fix above worked for me :smiley:

Is there anyway to get this to work without the any? Is there a specific type to put in it's place?

It's been a while but I thought I would come back with a better solution:

Create a custom definition file called react-router-dom.d.ts and add the following:

export * from "@types/react-router";

export { RouteComponentProps } from "react-router";

declare module "react-router-dom" {
  export function withRouter<T extends RouteComponentProps<any>>(
    component?: React.ComponentType<T>
  ): any;
}

This should then let you use @withRouter as a decorator without the @(withRouter as any) hack.

Was this page helpful?
0 / 5 - 0 ratings