curious-cat
curious-cat

Reputation: 421

Typescript Higher Order Component as Decorator

I am trying to use Typescript in my React project but am getting stuck at getting the types for my HOC's to function. Here is a minimal example to showcase what I am having trouble with:

const withDecorator =
    (Wrapped: React.ComponentType): React.ComponentClass =>
        class withDecorator extends Component {
            render() {
                return <Wrapped {...this.props} />
            }
        }

@withDecorator
class Link extends Component<object, object> {
    render() { return <a href="/">Link</a> }
}

This returns the error:

'Unable to resolve signature of class decorator when called as an expression.
Type 'ComponentClass<{}>' is not assignable to type 'typeof Link'.
    Type 'Component<{}, ComponentState>' is not assignable to type 'Link'.
    Types of property 'render' are incompatible.
        Type '() => string | number | false | Element | Element[] | ReactPortal | null' is not assignable to type '() => Element'.
        Type 'string | number | false | Element | Element[] | ReactPortal | null' is not assignable to type 'Element'.
            Type 'null' is not assignable to type 'Element'.'

I really don't understand why this error occurs. I must be doing something wrong. Things get even more hairy once I introduce props.

I would greatly appreciate the right solution, but I am also very interested in understanding why this error occurs in the first place.

Thanks!

Upvotes: 4

Views: 2770

Answers (1)

Adrian Leonhard
Adrian Leonhard

Reputation: 7360

A class decorator which returns a value is similar to doing

const Link = withDecorator(class extends Component<object, object> {
    render() { 
        return <a href="/">Link</a> 
    }
    instanceMethod() { return 2 }
    static classMethod() { return 2 }
})

TypeScript expects the return value of the decorator to have the same type as the input, so the result still has the same behavior. In your example, the render type signature does not match, but with the added methods the problem is more apparent: with your implementation of the decorator, the following would fail:

new Link().instanceMethod()
Link.classMethod()

The correct type signature would be:

function withDecorator<T extends React.ComponentClass>(Wrapped: T): T

and the implementation should match too, most easily by extending the target class:

return class extends Wrapped { ... }

Note that with React HOC, you don't necessarily want to extend the class, so using a decorator is possibly not the best solution.

See also https://github.com/Microsoft/TypeScript/issues/9453

Upvotes: 4

Related Questions