Skip to content

infer keyword in the prop types of React.forwardRef is always deducted to unknown #36502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kripod opened this issue Jan 29, 2020 · 7 comments
Closed
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@kripod
Copy link

kripod commented Jan 29, 2020

TypeScript Version: 3.8.0-dev.20200128

Search Terms: infer, forwardRef, generics

Code

// A *self-contained* demonstration of the problem follows...
// Test this by running `tsc` on the command-line, rather than through another build tool such as Gulp, Webpack, etc.

import React from 'react';

// Source: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/ADVANCED.md#extracting-prop-types-of-a-component
type ApparentComponentProps<
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  C extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = C extends React.JSXElementConstructor<infer P>
  ? JSX.LibraryManagedAttributes<C, P>
  : React.ComponentProps<C>;

export interface BoxProps<C = React.ElementType> {
  as?: C;
}

// Works fine
export function BoxWithoutRef<
  C extends
    | keyof JSX.IntrinsicElements
    | React.ComponentType<C extends React.ComponentType<infer P> ? P : never>
>({
  as,
  ...restProps
}: BoxProps<C> &
  React.PropsWithoutRef<ApparentComponentProps<C>>): JSX.Element {
  const Element: React.ElementType = as || 'div';
  return <Element {...restProps} />;
}

// Broken, although the code is mostly identical
export const Box = React.forwardRef(
  <
    C extends
      | keyof JSX.IntrinsicElements
      | React.ComponentType<C extends React.ComponentType<infer P> ? P : never>
  >(
    {
      as,
      ...restProps
    }: BoxProps<C> & React.PropsWithoutRef<ApparentComponentProps<C>>,
    ref: React.Ref<Element>,
  ) => {
    const Element: React.ElementType = as || 'div';
    return <Element ref={ref} {...restProps} />;
  },
);

function Example() {
  return (
    <>
      <div /* Type-checks fine*/>
        <BoxWithoutRef>1</BoxWithoutRef>
        <BoxWithoutRef invalidProp />

        <BoxWithoutRef as="a" href="https://github.com/kripod">
          2
        </BoxWithoutRef>
        <BoxWithoutRef href="https://github.com/kripod">3</BoxWithoutRef>

        <BoxWithoutRef as={CustomButton}>4</BoxWithoutRef>
        <BoxWithoutRef as={CustomButton} customProp={42}>
          5
        </BoxWithoutRef>
        <BoxWithoutRef as={CustomButton} customProp={42} href="https://github.com/kripod">
          6
        </BoxWithoutRef>
      </div>

      <div /* Broken */>
        <Box>1</Box>
        <Box invalidProp />

        <Box as="a" href="https://github.com/kripod">
          2
        </Box>
        <Box href="https://github.com/kripod">3</Box>

        <Box as={CustomButton}>4</Box>
        <Box as={CustomButton} customProp={42}>
          5
        </Box>
        <Box as={CustomButton} customProp={42} href="https://github.com/kripod">
          6
        </Box>
      </div>
    </>
  );
}

interface CustomButtonProps {
  children?: React.ReactNode;
  style?: React.CSSProperties;
  customProp: 42;
}

export default function CustomButton({
  style,
  ...restProps
}: CustomButtonProps): JSX.Element {
  return (
    <button
      type="button"
      style={{ fontSize: '3em', ...style }}
      {...restProps}
    />
  );
}

Expected behavior:
Box should behave exactly like BoxWithoutRef from a type-checking perspective.

Actual behavior:
React.ComponentType<C extends React.ComponentType<infer P> ? P : never> inside Box is always resolved as React.ComponentType<unknown>, causing false-positive type errors.

Playground Link: http://www.typescriptlang.org/play/?jsx=2&ts=3.8.0-dev.20200128&ssl=51&ssc=27&pln=51&pc=30#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wG4AocgeirgGUIBXKNJALjgAsYYwBnNjQDmwGJ0YAjAHRpcVGAE8wSPmijAwMALRpOqGHz1IDVIum2Llq9Zp16UBozCoSANhAlUQKPjCRQqAEEAEQA1QIA5AGEAUWCpEAATAGIkAA8YKHNgADshLTAcMC1LFS0ITC0UHVxIHKQcmHJSuECwMBQiRqjaiHrGgAUivgAecjg4GjgVV1ztROA+FDckLXqMrVn6uAABUusNbRm5qhyILXSwWbRRKpyFcbgo6YyGxL44AGskBQq4ACk6AANKQASUa6hyfGAaBiriQIAaBjgAB9EMQYFJAUC4QikT0oZlGBhoCMUPcAHzkClwAC8Txefhy73R5ixwNxiO6fV8UGJMFJuUw-jgAypEwA-ADgVIADLACRZKAKACy5JQQiQiUCPHUEkYflGUQANKLxXAOMg2T1wH0kUMIPwRlEKRRyJdoPA5v5MOgkHAAEIQNIOp3PelWjBSTlIgAqSiQNIA3o8fBKOFEKABfShTADq0E+H0wuSQ7rSkFgcEwjByGGAfUDwbzok4TBgyEwYwmz3STPejwmaO+v0w0pB4MyuWhsPhXIMg9RrKjNrqcYTzsZbw+kcxq7tjXjyhGQpFYrgUoGFrg9QAbv4qRSABQpiY+Y2PKRfoi+UN8chZhwQYhsMzo0gAZI8u5SH+LZiO2nYjG0HRdDA+79DAf5gRSACUHDYtGc5InAr5wLIhJwDGjSWhihF4oeCZ0nAPiomi+ALLeZCPEQMDMDkcAjFR8BJl+Ug-phwxZpMroAbmtABjg3w5KaKCuPBjBCJwcBiP6siJP6ixwCAEC+K4ChwMA+mNDCqnlpW8Dkb4TZpEx0GYNAADunSJJ2T6PN2EwMn226LkOXw-H8BGTpCM5Cf+gWBWi0Hoeux69q8zI7rRKUMcep5QGaF6ited4Po8z6LqRCXvqFcCieJf6LoBzlYS6cDgcumKwa2CFIF2yGdPivQYa1FIUh+CVEJgNFsohQnjY8OF0smi6OfAQkzVGQlHv69IsSibEcVxk3GHxAlCXAU20kmU1SSJ34qBJjp8FJVAyRMWYfjhbo1nWMANvxMRpCg4Dwk+S2kTxZ1+QlIzmrDHGTAAVHAO12EgaBFtWpZI29tUTCMwFwW2BqdhSACMIxUETPWk318MJQJNPqR2fUWTkt6qZZobSZQjOBYTza06zY4+LSABEKDi1wV3i9wvACMIraSDIcifDYECJOLDP8wATPjAnU0LLNkwbgtpMTvVjpwsvy-wghUCIYgq7IIBUOrGia9rADMVPMyTItUmb-tW8xfDXVEjC+LgAYGgKORZhSAAsfvGwHpv8wTId06L4dJpH0cgLHPB9FJaBRwKIChtdSe64nBsTAArGbRsW8LGeZ+bls52HEcVzHcel2R-dV0UNd1zLfUS3biuO8r0iu+7GtazrjMAGwt9nge1VTHFB-zIyI1QKMKRASlwLjq9Z8GFOp2kV9M8G7Oc7MiQ83jwdP2LkvSzbU9yzwe2StnYLzVsvbWDc4D60zobYCD9zaT0wNPQBs8nbiFAW7D2kAV6+1bvvTuwFe75xHsXeOicU54M-i5MWxDC6kKHuXQu1cky13rjApum8b5UKIQXSu9CE7DyYWPFhE8-5IIAQrB2aCXZgM9ivSBG8YF31XrvYAt4GZU3NN9WS5BvRQF9KwJ4JDB45D-CRR4uhgCuESF0dMnUpC7giJrJAFAJi+AUPCOxyU6B0FDP4f6KhXGCMrqGDgtdsyUA9FWfSvpGBqWrLWesjZeEDxLjkF8jx3HwgmnVB6v5hgAQzMYtJf48LjjovOcxEwoZQH4jDAW+o0m1VKBLRp8dxa1SyUga6SZqx9BgHQYAAAvdgBBvYInwKaUSXS4BZhzPze6YlHp-nmYFPGExtE5iAA

Related Issues: Possibly #9366

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Jan 29, 2020
@RyanCavanaugh
Copy link
Member

We'd need a simpler repro to be able to investigate this quickly

@kripod
Copy link
Author

kripod commented Jan 30, 2020

@RyanCavanaugh Thank you for the quick response! I created a concise reproduction available on the Playground:

import React from 'react';

type TestProps<T> = {
    children: T;
}

const CorrectComponent =
    <C extends React.ComponentType<C extends React.ComponentType<infer P> ? P : never>>(
        { children }: TestProps<C>,
    ) => {
        const Element: React.ElementType = children;
        return <Element />;
    }
;

const IncorrectComponent = React.forwardRef(
    <C extends React.ComponentType<C extends React.ComponentType<infer P> ? P : never>>(
        { children }: TestProps<C>,
        ref: React.Ref<Element>,
    ) => {
        const Element: React.ElementType = children;
        return <Element />;
    }
);

function Example() {
    return (
        <>
            <CorrectComponent>{CorrectComponent}</CorrectComponent>
            <CorrectComponent>{IncorrectComponent}</CorrectComponent>

            <IncorrectComponent>{CorrectComponent}</IncorrectComponent>
            <IncorrectComponent>{IncorrectComponent}</IncorrectComponent>
        </>
    );
}

In the latter case, P always gets inferred as unknown, breaking type checks for the children of IncorrectComponent.

@kripod
Copy link
Author

kripod commented Feb 11, 2020

This is possibly a duplicate of #9366. I apologize for not discovering that issue earlier.

@kripod kripod closed this as completed Feb 11, 2020
@mshwery
Copy link

mshwery commented Jul 17, 2020

As far as I can tell this is still not working properly in your first example, even on latest TS versions @kripod

@GuillaumeSpera
Copy link

I am pretty sure I have the same issue produced by this code.
I'm using "typescript": "^3.9.7"

@RyanCavanaugh Thank you for the quick response! I created a concise reproduction available on the Playground:

import React from 'react';

type TestProps<T> = {
    children: T;
}

const CorrectComponent =
    <C extends React.ComponentType<C extends React.ComponentType<infer P> ? P : never>>(
        { children }: TestProps<C>,
    ) => {
        const Element: React.ElementType = children;
        return <Element />;
    }
;

const IncorrectComponent = React.forwardRef(
    <C extends React.ComponentType<C extends React.ComponentType<infer P> ? P : never>>(
        { children }: TestProps<C>,
        ref: React.Ref<Element>,
    ) => {
        const Element: React.ElementType = children;
        return <Element />;
    }
);

function Example() {
    return (
        <>
            <CorrectComponent>{CorrectComponent}</CorrectComponent>
            <CorrectComponent>{IncorrectComponent}</CorrectComponent>

            <IncorrectComponent>{CorrectComponent}</IncorrectComponent>
            <IncorrectComponent>{IncorrectComponent}</IncorrectComponent>
        </>
    );
}

In the latter case, P always gets inferred as unknown, breaking type checks for the children of IncorrectComponent.

@GuillaumeSpera
Copy link

@kripod is the reported issue fixed ?
If so, could you share the final version plz ?
Otherwise, could you re-open this to allow further discussion ?

Thanks

@kripod
Copy link
Author

kripod commented Aug 16, 2020

The issue seems to be fixed.

@GuillaumeSpera Unfortunately, I'm not quite sure about the fix for my original code. As the problem has been fixed, I'm keeping this closed for now. Please open a new issue if you experience something wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

4 participants