You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Arguments to callback prop of generic React component are not inferred #44596
importReactfrom'react';typeProps<TextendsReact.ElementType>={as: T}&Omit<React.ComponentPropsWithoutRef<T>,'as'>;declarefunctionButton<TextendsReact.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.<Buttonas="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.
I've looked into this some more. If I make the onClick prop required (i.e. remove the | undefined), the inference starts working correctly. However, this is obviously not desirable, since the onClick prop is optional for a reason.
importReactfrom'react';typeMakeRequired<T,K>=KextendskeyofT ? Omit<T,K>&Required<Pick<T,K>> : T;typeProps<TextendsReact.ElementType>={as: T}&MakeRequired<Omit<React.ComponentPropsWithoutRef<T>,'as'>,'onClick'>;declarefunctionButton<TextendsReact.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.<Buttonas="button"// OK: The type of `onClick` is `(JSX attribute) onClick: React.MouseEventHandler<HTMLButtonElement>`.// OK: The type of `e` is `(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>`.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>`.// OK: The type of `e` is `(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>`onClick: (e)=>{console.log(e);},});
We can take a look earlier if someone can remove the react dependency from the repro, since it's entirely possible there's something in the definition that causes this per expected behavior.
After reading your comment I also searched through the issue tracker on DefinitelyTyped. There I found this issue for @types/react which 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 the react dependency.
I've altered my original code sample to no longer include the react dependency. 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.
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 the onClick callback (including its event argument), but not propagate that information to the type of the actual argument e in the provided callback.
Edit: I see now that the JSX namespace is no longer included, leading to some squiggly red error lines. Adding import 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.
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 the Pick<> and it still seems to work.
- type MakeRequired<T, K> = K extends keyof T ? Omit<T, K> & Required<Pick<T, K>> : T;+ type FixCallback<T, K> = K extends keyof T ? Omit<T, K> & Pick<T, K> : T;
More interestingly, applying this fix to the onClick handler only will also make the onBlur event handler work in the function case, but NOT in the JSX case!
importReactfrom'react';typeFixCallback<T,K>=KextendskeyofT ? Omit<T,K>&Pick<T,K> : T;typeProps<TextendsReact.ElementType>={as: T}&FixCallback<Omit<React.ComponentPropsWithoutRef<T>,'as'>,'onClick'>;declarefunctionButton<TextendsReact.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.<Buttonas="button"// OK: The type of `onClick` is `(JSX attribute) onClick: React.MouseEventHandler<HTMLButtonElement>`.// OK: The type of `e` is `(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>`.onClick={(e)=>{console.log(e);}}// NOT OK: The type of `e` is `any`.onBlur={(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>`.// OK: The type of `e` is `(parameter) e: React.MouseEvent<HTMLButtonElement, MouseEvent>`.onClick: (e)=>{console.log(e);},// OK (Somehow?!): The type of `e` is `(parameter) e: React.FocusEvent<HTMLButtonElement>`.onBlur: (e)=>{console.log(e);},});
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.
We can take a look earlier if someone can remove the react dependency from the repro, since it's entirely possible there's something in the definition that causes this per expected behavior.
importReactfrom"react";declareconstIdentity:
<CextendskeyofJSX.IntrinsicElements>(props: {as?: C}&JSX.IntrinsicElements[C])=>JSX.Element<Identityas="input"onChange={value=>{// value is any, expected string}}value="foo"/>Identity({as: "input",onChange: value=>{// value is string, expected string},value: "foo"})
We can take a look earlier if someone can remove the react dependency from the repro, since it's entirely possible there's something in the definition that causes this per expected behavior.
Ryan Cavanaugh (@RyanCavanaugh) OK, I now have an actualreact-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.
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.
<Boxdisplay="flex"borderWidth="thick"/>
Which can also use as:
<Boxas="input"type="email"/><Boxas={Link}to="/home"/>// react-router-dom link
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, ComponentPropsWithRef on your type. With some massaging I was able to get it to work.
You may have noticed that ComponentPropsWithoutRef seems to work fine, but ComponentPropsWithRef breaks 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.
The issue is because of ElementType without any given parameter as it's type parameter defaults to any. See my last comment how I fixed it. May be you can infer the type parameter of ElementType to fix it.
Seems like the problem occur just when using Omit or ComponentProps.
The reason why ComponentPropsWithoutRef works and ComponentProps doesn't is because ComponentPropsWithoutRef uses PropsWithoutRef, 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:
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