2

My intent is to extract the props out of a given JSX element, is it possible?

This was pretty much my failed attempt... Thanks in advance for any help ;)

function getComponentProps<T extends React.ReactElement>(element: T): ExtractProps<T>;

function Component({ name }: { name: string }) {
  return <h1>{name}</h1>;
}

type ExtractProps<TComponentOrTProps> = TComponentOrTProps extends React.ComponentType<infer TProps>
  ? TProps
  : TComponentOrTProps;

const componentProps = getComponentProps(<Component name="jon" />); //type is JSX.Element
Pedro Figueiredo
  • 2,304
  • 1
  • 11
  • 18

3 Answers3

5

For the most part, you can't do this.

In theory, the React.ReactElement type is generic with a type parameter P that depends on the props. So if you were to have a strongly-typed element then you could work backwards.

type ElementProps<T extends React.ReactNode> = T extends React.ReactElement<infer P> ? P : never;

In reality, you will only get a correct props type if you create your element through React.createElement rather than JSX.

Any JSX element <Component name="John" /> just gets the type JSX.Element which obviously has no information about the props so you cannot work backwards from that to a props type.

const e1 = React.createElement(
  Component,
  { name: 'John' }
)

type P1 = ElementProps<typeof e1> // type: {name: string}

console.log(getElementProps(e1)); // will log {name: "John"}
const e2 = <Component name="John" />

type P2 = ElementProps<typeof e2>  // type: any

console.log(getElementProps(e2)); // will log {name: "John"}

Playground Link

It is much easier to approach the situation from a different angle. You will be able to derive the correct props type if your function takes a component like Component or div rather than a resolved element. You can use the ComponentProps utility type for function and class components and the JSX.IntrinsicElements map for built-in ones.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
2

You can extract type of the Props for any component using

React.ComponentProps<typeof T>

You can refer this TS Playground for more options

import * as React from 'react';

type TProps = {
   name:string;
   age:number;
   isStackoverflow:boolean;
 }

const App = (props:TProps) => <div>Hello World</div>;

//You can extract any components props like this.
type AppProps = React.ComponentProps<typeof App>;

`

Rajiv Punjabi
  • 486
  • 3
  • 6
1

This is not possible for getComponentProps(<Component name="jon" />);, since written out JSX-Elements always result in the JSX.Element-type which doesn't give any additional type information which you could extract. It would be possible if you extract it from the component function itself:

export function Component({ name }: { name: string}) {
    return <h1>{name}</h1>;
}

function getComponentProps<T extends (...args: any[]) => JSX.Element>(element: T): Parameters<T>[0] {
    return null as any;
}

const test = getComponentProps(Component); // { name: string;}

This solution uses the utility type parameter, which infers all arguments from a function. We then index the first argument since the prop object is the first argument of a pure jsx function. Class components would need a different solution, though.

Mirco S.
  • 2,510
  • 4
  • 13
  • Hum... Yeah, unfortunately what I'm trying isn't achievable then. As I wanted to take a JSX element (probably already with some props) and then return a clone of it which takes the same type of props... Thanks a lot for the help anyway dude ;) – Pedro Figueiredo Mar 22 '21 at 21:29
  • 1
    @PedroFigueiredo what is the actual use case where you would need to know what props have already been passed? It's possible that what you want is some manipulation with `React.children` and `cloneElement`. – Linda Paiste Mar 22 '21 at 21:36
  • It's something like this, where I take a component a clone it with the same props it already takes: https://cutt.ly/XxxZEYe – Pedro Figueiredo Mar 22 '21 at 21:44
  • So you can take a component and return a component -- that's [just an HOC](https://tsplay.dev/Wyv6dw) and I've typed those a million times :) You can take an element and return an element [like this](https://tsplay.dev/WvpGRN). Taking an element and returning a component is something that you can do but it's a bit weird and you're going to have a hard time getting good type support. – Linda Paiste Mar 22 '21 at 22:00
  • In *theory*, `React.ReactElement` is generic, but you only see those values getting set if you create it through `React.createElement` rather than JSX. – Linda Paiste Mar 22 '21 at 22:02
  • 2
    [How about this?](https://tsplay.dev/W44neW) assigning partial props to intrinsic JSX elements. – Linda Paiste Mar 22 '21 at 22:12
  • Well, actually thanks a lot @Linda (again :D), I will probably have to build something around this last comment of yours. In reality I'm trying to build a tiny lib that takes in some form controls and returns them based on a few defined options. https://github.com/pffigueiredo/react-formgen – Pedro Figueiredo Mar 22 '21 at 22:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230239/discussion-between-pedro-figueiredo-and-linda-paiste). – Pedro Figueiredo Mar 22 '21 at 22:25