1

This is an offshoot of TypeScript type for React FunctionComponent that returns exactly one IntrinsicElement.

React defines

declare namespace JSX {
  type Element = React.ReactElement<any, any>;
}

And while I can't find it in @types/react/index.d.ts, rendering a <Fragment> returns a JSX.Element. So, if I want to define a type for a function that returns exactly one HTML element (i.e. not a Fragment unless it has exactly one child, which would defeat its purpose), I somehow need to limit my return type to something like

type IntrinsicFC = 
  () => Exclude<JSX.IntrinsicElements[keyof JSX.IntrinsicElements], ReactElement<any, any>>;

(Note: Exclude<..., ReactElement<any, any>> does not mean what I expected it to; it seems to be identical to JSX.IntrinsicElements[keyof JSX.IntrinsicElements] though I don't understand why. But, for the sake of this question, assume I did know the right type for what I tried to express.)

However, any is the death of strong typing, and since every HTML Element extends ReactElement<any, any>, this would make my type () => never.

Is there a way to exclude a type which is a type alias for SomeGenericType<any>?

My latest failed attempt at making an IntrinsicFC is:

type IntrinsicElement = JSX.IntrinsicElements[keyof JSX.IntrinsicElements];

type IntrinsicFC<P = void> =
  ((props: P) => IntrinsicElement) extends ((props: P) => JSX.IntrinsicElements[infer U & keyof JSX.IntrinsicElements])
    ? U extends keyof JSX.IntrinsicElements
      ? ((props: P) => JSX.IntrinsicElements[U])
      : never
    : never;

This, however, results in type IntrinsicFC<P = {}> = never;, because IntrinsicElement always extends JSX.IntrinsicElements[keyof JSX.IntrinsicElements] as it is the very same definition.

TS Playground


I suspected that when the React team wrote the declaration file, they meant JSX.Element to be defined as

type Element = React.ReactElement<unknown>;

but that the type predates TypeScript v3.0.0, which introduced the unknown type, and changing it now would be a breaking change. However, I just tried manually changing the type declaration, and it still doesn't work, so... ‍♀️

dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
  • 1
    Uh, blagh, this feels like a job for a linter or a library change. Otherwise you might end up going down the route of [detecting and disallowing `any`](https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360) which is yucky. – jcalz Jul 23 '21 at 16:45
  • Actually I don't quite understand what type you want `IntrinsicFC` to be. If this question is really about react, you might want to tag it as such. If not, then maybe could you provide a [mcve] that doesn't depend on it? `JSX.IntrinsicElements[keyof JSX.IntrinsicElements]` has over 120 members and I can't tell which, if any of them, you want to exclude. Or if you want to just *transform* each of them to something else, that makes sense, but maybe reduce the problem to just a few input-output relationships. – jcalz Jul 23 '21 at 16:52
  • Okay, I'll come up with a more minimal example. – dx_over_dt Jul 23 '21 at 16:53
  • Hmm, the more I've looked at this, the more I'm thinking it's impossible. `const x =
    ;` *also* makes `x` a `JSX.Element` unless you specifically tell it to be something else. And even if I do something like `const x: JSX.IntrinsicElements['a'] = foo`, it complains that `Element` is missing properties of `JSX.IntrinsicElements['a']`.
    – dx_over_dt Jul 23 '21 at 17:16

0 Answers0