5

I have the following function which I want to use to take in a ComponentType and its props in order to allow me to inject those props along with the RouteComponentProps

const routeComponentFactory = <TProps extends {}>(
    Component: React.ComponentType<TProps>,
    props: TProps
) => {
    return (routeProps: RouteComponentProps) => {
        return <Component {...routeProps} {...props} />;
    };
};

This function works correctly if I explicitly specify TProps, for example:

interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;

routeComponentFactory<MyComponentProps>(MyComponent, {});

I get an error there for not providing a and b in the object.

However, if I remove the explicit <MyComponentProps>, the error goes away and I am allowed to call the function with an empty object.

How can I make TypeScript properly infer MyComponentProps?

2 Answers 2

4

If you enable strictFunctionTypes you will get an error:

Type 'FunctionComponent<MyComponentProps>' is not assignable to type 'FunctionComponent<{}>'.

There are two possible inference sites for TProps the arguments Component and props both contain the type parameter TProps so typescript tries to find a type that will make both sites happy. Since if TProps were {} both the argument {} and the props type MyComponentProps would be assignable to it typescript infers TProps to be {} in order to keep everyone happy.

The reason that under strictFunctionTypes you do get an error is that by default function type behave bivariantly (so a function (p: MyComponentProps) => JSX.Element is assignable to (p: {}) => JSX.Element). Under strictFunctionTypes function types behave contravariantly so such an assignment is disallowed.

The solution to get an error even without strictFunctionTypes is to decrease the priority of the props inference site, so the compiler picks what is good for Component and checks it against props. This can be done using an intersection with {}:

const routeComponentFactory = <TProps extends {}>(
    Component: React.ComponentType<TProps>,
    props: TProps & {}
) => {
    return (routeProps: any) => {
        return <Component {...routeProps} {...props} />;
    };
};

interface MyComponentProps { a: number; b: number; }
const MyComponent: React.FunctionComponent<MyComponentProps> = () => null;


routeComponentFactory(MyComponent, {}); // Argument of type '{}' is not assignable to parameter of type 'MyComponentProps'
Sign up to request clarification or add additional context in comments.

Comments

1

I cannot see it allowing me to call the function with an empty object but in my case it is complaining that the MyComponent prop is invalid.

It seems the problem is it is inferring the type from the second parameter - I guess because it is matching on the simplest type.

You could define your function like this to force it to infer the correct type:

const routeComponentFactory = <TProps extends {}>(
    Component: React.ComponentType<TProps>
) => (
    props: TProps
) => {
    return (routeProps: RouteComponentProps) => {
        return <Component {...routeProps} {...props} />;
    };
};

And then call it like this:

routeComponentFactory(MyComponent)({});

And it should complain about the empty object correctly in this case.

1 Comment

Not a bad option, but function currying is not necesary in this case

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.