Repository: TypeScript
Issue: Allow different properties between the JSX expression and component type
Labels: Suggestion,Awaiting More Feedback

# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

* jsx functional component properties
* property type
* parameter type 
* jsx property cast

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).

## ⭐ Suggestion

Concept based on #14729

Introduce a new function to the `JSX` namespace called `convertProperties` (subject to change) which takes a component and returns an object representing its JSX props.

```ts
declare namespace JSX {
    // today's behavior
    function convertProperties<P>(component: (JSX.ElementClass & { new(): { props: P } }) | ((props: P) => JSX.Element)): P;

    // alternatively, if this were to supercede IntrinsicAttributes rather than just working alongside it
    function convertProperties<P>(component: (props: P) => JSX.Element): P & JSX.IntrinsicAttributes;
    function convertProperties<P, T extends { props: P }>(component: JSX.ElementClass & { new(): T }): P & JSX.IntrinsicAttributes & JSX.IntrinsicClassAttributes<T>;
}
```

When the typechecker sees a component expression (e.g. `<MyComponent prop={val} />`), it will determine the types of the required properties by performing a virtual call to `JSX.convertProperties` with the provided component type and using the return type. For example:

```ts
type SignalProps<P> = { [K in keyof P]: () => P[K] };

declare namespace JSX {
    function convertProperties<P>(component: ((props: SignalProps<P>) => JSX.Element)): P;
}

type MyProps = SignalProps<{
    title: string;
    count: number;
}>;

function MyComponent({ title, count }: MyProps) {
    return <div>{title()}: {count()}</div>;
}

function App() {
    // typeof MyComponent == (props: { title: () => string, count: () => number }) => JSX.Element
    // typeof JSX.convertProperties(MyComponent) == { title: string, count: number }
    return <MyComponent title="Hello, World!" count={3} />;
}
```

## 📃 Motivating Example

The [Solid](https://www.solidjs.com/) framework is very similar to React, but rather than tracking reactive dependencies with an explicit dependencies array like in React's `useMemo` and `useEffect`, Solid requires calling a function to retrieve the value of a piece of state, allowing for implicit dependency tracking based on which states were previously fetched the last time the memo/effect callback ran (see https://www.solidjs.com/guides/reactivity#introducing-primitives). It is a similar concept to [Knockout's observables](https://knockoutjs.com/documentation/observables.html#observables).

However, when it comes to component properties, Solid uses proxy dereferences instead of explicit function calls. This leads to a common footgun where destructuring the `props` object like one might do in React will prevent dependencies from being tracked (https://www.solidjs.com/guides/faq#why-do-i-lose-reactivity-when-i-destructure-props). An alternative design could have had the jsx factory function wrap every prop in a reactive getter before bundling them into the `props` object.

By separating the prop types between the component creation and the JSX where it's used, TypeScript would be able to support such a design in a new web framework.

## 💻 Use Cases

See above.

Another similar use case could be:

```ts
type DetailedProps<P> = {
    [K in keyof P]: {
        value: P[K];
        prevValue?: P[K];
    }
}

declare namespace JSX {
    function convertProperties<P>(component: ((props: DetailedProps<P>) => JSX.Element)): P;
}
```

This feature would also allow performing the reverse operation of `JSX.IntrinsicAttributes`. For example:

```ts
declare namespace JSX {
    function convertProperties<P>(component: ((props: P) => JSX.Element)): Omit<P, 'parent'>;
}

type PropsWithParent<T> = T & {
    parent?: JSX.Element;
};

function MyComponent({ parent }: PropsWithParent<{}>) {
    // ...
}

function App() {
    return <MyComponent />;
}
```

## Alternative Designs

Alternatively, a solution which is slightly less powerful but likely just as useful in 90% of cases could be to declare generic types on the `JSX` namespace like is done with `JSX.IntrinsicClassAttributes<T>`:

```ts
declare namespace JSX {
    // same behavior as today
    type TransformProps<P> = P;

    // some use cases
    type TransformProps<P> = { [K in keyof P]: P[K] extends () => infer R ? R : never };
    type TransformProps<P> = Omit<P, 'parent'>;

    // or in reverse, deriving JSX prop types via inference like in the earlier examples
    type TransformProps<P> = SignalProps<P>;
    type TransformProps<P> = P & { parent?: JSX.Element };
}
```