2

I simplified the code for readability.

interface OwnProps<T extends ElementType> {
    as?: T;
}

type Props<T extends ElementType> = OwnProps<T> & ComponentPropsWithoutRef<T>;

const Component = <T extends ElementType = 'button'>({as, ...rest}: Props<T>) => {
    const Tag = as || 'button';

    return <Tag {...rest} />;
};

And when I'm trying to use Component I get a TS error:

TS7006: Parameter 'e' implicitly has an 'any' type.
    57 |
  > 58 | <Component onClick={(e) => e}/>;
       |                      ^
    59 |

Sounds weird, but any other props except event handlers work correctly.

1 Answer 1

3

When you create a function in TypeScript which uses a generic type parameter, you must specify the type parameter when invoking the function (unless it can be inferred from an argument).

A React function component is just a function, so when you invoke the function component in your JSX, you must still supply the generic type parameter in order for the compiler to have that type information.

In the code shown in the error message of your question, you don't do that, so the event type is unknown to the compiler:

const reactElement = <Component onClick={(e) => e}/>; /*
                                          ~
Parameter 'e' implicitly has an 'any' type.(7006) */

By supplying a valid generic type parameter for the element, the compiler will infer the event type:

const reactElement = <Component<'div'> onClick={(e) => e}/>; // Ok!
                                               //^? (parameter) e: React.MouseEvent<HTMLDivElement, MouseEvent>

In the component you showed, the as prop should probably not be optional. When you supply the as prop, the compiler will infer the generic from its value:

const reactElement = <Component as="div" onClick={(e) => e}/>; // Ok!
                                                 //^? (parameter) e: React.MouseEvent<HTMLDivElement, MouseEvent>

Code in TypeScript Playground


If you must use a pattern of accepting the as prop optionally (with a default element type of "button" when it is not provided, you can use a function overload signature to make that clear to the compiler:

TS Playground

import {
  default as React,
  type ComponentPropsWithoutRef,
  type ElementType,
  type ReactElement,
} from 'react';

function Component <T extends ElementType>({as, ...rest}: { as: T } & ComponentPropsWithoutRef<T>): ReactElement;
function Component ({as, ...rest}: { as?: never } & ComponentPropsWithoutRef<'button'>): ReactElement;
function Component <T extends ElementType>({as, ...rest}: { as?: T } & ComponentPropsWithoutRef<T>) {
  const Tag = as ?? 'button';
  return <Tag {...rest} />;
}

const reactElement1 = <Component onClick={(e) => e}/>;
                                         //^? (parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>

const reactElement2 = <Component as="div" onClick={(e) => e}/>;
                                                  //^? (parameter) e: React.MouseEvent<HTMLDivElement, MouseEvent>

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the long explanation! You saved my day 🤗 Only 1 thing: you don't need to use polymorphism IMO (see my answer). ```
^ @vcarel Thanks for the feedback — it seems like maybe that is directed at the asker: I was simply responding to their question.
Sorry, my solution doesn't work well... callbacks like onClick are always typed with HTMLButtonElement and not with the provided "as"

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.