Repository navigation
Closed
Labels
BugA bug in TypeScriptA bug in TypeScriptFix AvailableA PR has been opened for this issueA PR has been opened for this issue
Milestone
Description
Bug Report
🔎 Search Terms
react, polymorphic components, generic components, type inference on function arguments
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about type inference on function arguments
⏯ Playground Link
Playground link with relevant code
💻 Code
import React from 'react';
type Props<T extends React.ElementType> =
{ as: T } & Omit<React.ComponentPropsWithoutRef<T>, 'as'>;
declare function Button<T extends React.ElementType>(props: Props<T>): JSX.Element;
// Using Button as React component (intended use case):
// OK: The type of `Button` is `function Button<"button">(props: Props<"button">): JSX.Element` in this instance.
<Button
as="button"
// OK: The type of `onClick` is `(JSX attribute) onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined`.
// NOT OK: The type of `e` is `any` (implicitly).
onClick={(e) => { console.log(e); }}
/>;
// Using Button as regular function (same issue):
// OK: The type of `Button` is `function Button<"button">(props: Props<"button">): JSX.Element` in this instance.
Button({
as: 'button',
// OK: The type of `onClick` is `(property) onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined`.
// NOT OK: The type of `e` is `any` (implicitly).
onClick: (e) => { console.log(e); },
});🙁 Actual behavior
The argument e to the callback function provided to onClick implicitly has any type.
🙂 Expected behavior
I would expect the checker to know that the type of e can only be React.MouseEvent<HTMLButtonElement, MouseEvent>.
The checker seems to know that the type of the onClick callback is React.MouseEventHandler<HTMLButtonElement>, which in turn resolves to (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void. So I would expect the checker to be able to use that information to determine the type of e / event.
Reacted by tu4mo, Joe Calzaretta, Aylon Carrijo, Evan Jacobs, Daniel Horvath, Dan Antal and Treycos
Reacted by yossarian, Aylon Carrijo, Evan Jacobs, Daniel Horvath, Honza Gál and Treycos
Metadata
Metadata
Assignees
Labels
BugA bug in TypeScriptA bug in TypeScriptFix AvailableA PR has been opened for this issueA PR has been opened for this issue
Activity
Mesoptier commentedon Jun 15, 2021
I've looked into this some more. If I make the
onClickprop required (i.e. remove the| undefined), the inference starts working correctly. However, this is obviously not desirable, since theonClickprop is optional for a reason.Playground link with relevant code
RyanCavanaugh commentedon Jun 15, 2021
We can take a look earlier if someone can remove the
reactdependency from the repro, since it's entirely possible there's something in the definition that causes this per expected behavior.Mesoptier commentedon Jun 15, 2021
After reading your comment I also searched through the issue tracker on DefinitelyTyped. There I found this issue for
@types/reactwhich looks similar to what I'm describing: DefinitelyTyped/DefinitelyTyped#50459. According to that, this might an issue with the React typings. However, I'll continue trying to reproduce the issue without thereactdependency.Mesoptier commentedon Jun 15, 2021
I've altered my original code sample to no longer include the
reactdependency. Instead it now includes a few minimal types based on those found in@types/react, which is still enough to demonstrate the issue somewhat succinctly. Hopefully this will help in narrowing down on the issue.Playground link
Interestingly, if I comment line 19 (
| ElementConstructor<P>), the issue disappears.Playground link with relevant line commented
I can see how this might suggest that the issue lies within
@types/react. However, the fact remains that TypeScript seems to be able to correctly resolve the type of theonClickcallback (including itseventargument), but not propagate that information to the type of the actual argumentein the provided callback.Edit: I see now that the
JSXnamespace is no longer included, leading to some squiggly red error lines. Addingimport React from 'react';should fix those. Then the dependency is only used for TypeScript to understand the JSX part and does not impact the issue described in any way.Mesoptier commentedon Jun 17, 2021
Looking at this comment from earlier, I realised that it probably wasn't the "making the prop required" part of it all that made it work. I removed the
Required<>utility type, and kept only thePick<>and it still seems to work.More interestingly, applying this fix to the
onClickhandler only will also make theonBlurevent handler work in the function case, but NOT in the JSX case!Playground link
quantizor commentedon Dec 18, 2021
Ryan Cavanaugh (@RyanCavanaugh) I see that this is marked as backlog. A lot of people in the FE world use this prop inference pattern... would be good to at least target a milestone for fixing it.
devanshj commentedon Dec 18, 2021
Here's the react free version Ryan Cavanaugh (@RyanCavanaugh) asked for. Playground. (Uses react import just for the
JSX.IntrinsicElementsdefinition).Mesoptier commentedon Mar 29, 2022
Ryan Cavanaugh (@RyanCavanaugh) OK, I now have an actual
react-free reproduction case for this issue. One thing to note is that I've set TypeScript's JSX option to 'Preserve', to avoid errors from using JSX syntax without a React import.Playground link
It still seems so weird to me that the type of the callback is correct, but the type of the callback parameter is not:
RyanCavanaugh commentedon Mar 29, 2022
Koen Klaren (@Mesoptier) thanks, that's a great repro
MartinJohns commentedon Sep 20, 2022
#44596 (comment) works in 4.8.2, but the original code example by Koen Klaren (@Mesoptier) still does not work.
gynekolog commentedon Sep 20, 2022
The issue in the original code comes with the Omit in the Props definition.
I haven't solution for the issue but I can share playground link with the original code extended by Props without Omit.
cristatus commentedon Sep 20, 2022
I have a similar case and it's still not working:
See the playground link.
cristatus commentedon Oct 6, 2022
I fixed my issue like this:
See the playground link.
So the issue seems to be associated with type inference when some of the type parameters are omitted?
corbanbrook commentedon Nov 11, 2022
I have been playing around with building a polymorphic box component which takes native html props as well as atomic class names from vanilla-extract sprinkles.
Which can also use as:
I came up against the problem of event handlers not typing the event properly and like others have noted here it is related to using types like
Omit,Pick,ComponentPropsWithRefon your type. With some massaging I was able to get it to work.You may have noticed that
ComponentPropsWithoutRefseems to work fine, butComponentPropsWithRefbreaks when used on the top level of your type. So the below example uses and intersection of{ref?: Ref<ComponentPropsWithRef<T>['ref']> | null}to get around the issues mentioned above.This example is largely based on the original types from the react-polymorphic-box lib.
playground link
sscaff1 commentedon Jan 12, 2023
Honza Gál (@gynekolog) I'm not omitting any props, but I do want to set a default for the ElementType and apparently that causes the issue again.
Here is your snippet with as an optional prop Link
Again it knows the type of the onClick, but it says the event is any. What's strange is that this is the same thing:
https://codesandbox.io/s/intelligent-marco-qdfwy4?file=/src/App.tsx
And yet codesandbox doesn't complain. When I try to do the same thing in a CRA project, I get the implicit any error as well.
cristatus commentedon Jan 13, 2023
The issue is because of
ElementTypewithout any given parameter as it's type parameter defaults toany. See my last comment how I fixed it. May be you can infer the type parameter ofElementTypeto fix it.sscaff1 commentedon Jan 13, 2023
I simplified your example and it still appears to work Link
What's strange is that if I paste my example, it also seems to work... I'm not sure what's going on.
cristatus commentedon Jan 15, 2023
I see. May be it's fixed in some recent version.
cristatus commentedon Jan 15, 2023
The type of
asparam in your original code should be inferred to make it work. You are passing the type directly.ayloncarrijo commentedon Feb 28, 2023
Seems like the problem occur just when using
OmitorComponentProps.The reason why
ComponentPropsWithoutRefworks andComponentPropsdoesn't is becauseComponentPropsWithoutRefusesPropsWithoutRef, which uses a conditional type to omit the "ref" attribute. This gives us a workaround to fix the problem.For now, using a conditional type on the omitted type seems to be enough to solve this:
Playground