TypeScript
TypeScript copied to clipboard
Decouple jsx element type from jsx factory return type and sfc return type
We need to look up the type of a jsx expression by actually resolving the jsx factory call, so that we don't create a reference to the global JSX.Element type, which can change shape between react versions (as it needs to in the react 16 upgrade). We also need to resolve the sfc return type and class element type from the parameters of the factory function overloads for the same reasons, doubly so because the types allowable as render method and SFC return values are no longer the same as JSX.Element (namely, they can be strings, arrays, portals, etc).
This might be considered a breaking change, because some consumers may expect JSX.Element to always be a supertype of both jsx element expression return types and SFC return types (even though this isn't true in react 16) - we certainly made that assumption internally, hence the need for the change. 🐱
@weswigham would addressing this address https://github.com/Microsoft/TypeScript/issues/18357 ? It'd be great to be able to correctly type children render props in React.
There are a lot of issues related to this that have been closed, saying that this issue will handle them. I am assuming that the case in #18357 will be handled by this as well.
At the core of this is the ability to type JSX based off of the createElement function, rather than using the set of interfaces in the JSX namespace. If that feature is indeed going to be implemented as part of this issue, then a natural extension would be that the children could end up anywhere in the resulting element, and they could be typed based off of anything, rather than just restricting children to the props.
@jchitel agreed. I just hit this snag trying to change the react typings yesterday for this exact same reason.
@weswigham If you haven't started on this yet, I'd like to take a crack at it
@ericanderson I actually started work on this today, sorry 😉
@weswigham Damn. Can you make sure I can capture the SFC or ComponentClass so we can do things like limit the children of <ButtonGroup> to <Button>?
@ericanderson That's the plan. (I mean, technically it was a bug report a long time ago that we closed as "fixed" even though it was only half-fixed)
I think that will also let us mark the defaultProps optional.
By defaultProps do you mean the intrinsic props?
I mean:
interface Props {
foo: string,
bar: number
}
class Foo extends React.Component<Props> {
static defaultProps = { foo: "Hi Mom!" };
//...
}
In this world, the compiler should only require me to specify bar and foo should now be considered foo?: string
... maybe. Even with localized types looked up from the signatuers, we still have to make assumptions about how the custom ctor/sfc is effectively built, because there's actually two calls going on. The first call is the constructor for the class or the SFC, (which are expected to take props as an argument), and can be overloaded - this is where a lot of props validation actually happens. The second call is the factory function itself, which actually needs the reified types from the inner call to be typechecked correctly (And today isn't actually typechecked at all, which is the root of most of these issues).
TBQH a lot of the JSX machinery in-place today is to get higher-ordery behavior from a system that used to not support any. I think now that we have conditional and infer types, there's a good chance I may actually be able to desugar a lot of the magic currently applied to JSX to normal typesystem operations. I'm generally just going to look at improving this as much as is feasible 😉
So the props we should enforce are actually Omit<Props, typeof Foo.defaultProps> & Partial<typeof Foo.defaultProps>... which maybe we can't do with this change.
@weswigham Thanks!
I'm not entirely sure that this was technically fixed. #18131 was fixed, but we still don't actually look at what a given JSX factory invocation returns.
Correct.
What are the technical impediments to doing ReturnType<factoryFunction> in typechecking JSX?
...or do I have absolutely no idea how any of this stuff works.
ReturnType doesn't use overload resolution, nor does it apply type argument inference, both which utilize the actual props passed in to a JSX element.
So you'd potentially get an "under-instantiated" and/or broad type depending on if your JSX factory is generic or has overloads (or both).
Oh yeah, now I remember reading that caveat to ReturnType<>. Bummer.
This improvement still won't allow recursively enforcing type constraints on JSX children, right? The use case I'm thinking of is React's new Context API.
// good
render() {
let {Consumer, Provider} = React.createContext(undefined)
return <Provider value={{a: 1}}>
<Consumer>
{state => state.a}
</Consumer>
</Provider>
}
// bad
render() {
let {Consumer, Provider} = React.createContext(undefined)
return <Consumer>
{state =>
<div>
<Provider value={{a: 1}}>
{state.a} // runtime error
</Provider>
</div>
}
</Consumer>
}
This has been an issue for so long. When will we get a fix for this? My issue is similar to those described in the thread with typings children of ButtonGroup to only take Button. My issue is that i have RenderProp components that enhance elements however they break when passed a Component instance instead of a native JSX.Element and there is no way to type against it currently.
a Component instance I.E <ButtonGroup/> is inherently different to say a native jsx element <div/> they have different properties and different behaviors and we need a way to type them accordingly.
- type on <ButtonGroup/> is a function type on native is a string.
- refs and event bindings behave differently when passed to one or the other.
- ReactDOM behaves differently when passed one or the other.
- refs when applied behave differently consider
ref.getBoundingClientRect();being called on a ref of a component instance vs a native jsx element.
I heard rumors this would be fixed with typescript 2.8, and then 2.9 but still nothing. see comments for https://stackoverflow.com/questions/49269743/protect-against-react-instances with references to github.com/Microsoft/TypeScript/issues/21699
Just to clarify, I understand that this issue is expected to allow us to define, for example, something like flowtype's https://flow.org/en/docs/react/children/#toc-only-allowing-a-specific-element-type-as-children
//… from flowtype's documentation
type Props = {
children: React.ChildrenArray<React.Element<typeof TabBarIOSItem>>,
};
Am I right?
This feature could be a really big deal to use TypeScript in our Project (made with JS + React, looking for type-check it), because we have a lof of JSX components that we would like to type-check its children elements, and currently it is not posible to do with TypeScript.
Yes, technically. You can already define such a type in TS, it just isn't terribly useful since every JSXExpression is erased to the base JSXElement.
Related: https://github.com/Microsoft/TypeScript/issues/14789
Does this fix the following problem?
let foo = React.createElement(Foo)
foo = <Foo />
/* [ts]
Type 'Element' is not assignable to type 'ComponentElement<{}, Foo>'.
Types of property 'type' are incompatible.
Type 'string | ComponentClass<any, any> | StatelessComponent<any>' is not assignable to type 'ComponentClass<{}, any>'.
Type 'string' is not assignable to type 'ComponentClass<{}, any>'. */
If not, does the above problem have a fix in the works?
I expect <Foo /> to return a React.ComponentElement<Foo['props'], Foo> value, not a JSX.Element value. At least when I'm using "jsx": "react" in tsconfig.json.
Maybe I can work around this by editing/overriding the exports of @types/react?
@aleclarson Yes, this would be the change which would hopefully allow that.
@weswigham Awesome!
I actually started work on this today, sorry 😉
Is it still a high priority for you? 😎
edit: Oh, looks like it's in-progress still. Can't wait!
Yeah, we got a lot of the way where with the new LibraryManagedAttributes entrypoint we added for react last release; but that just fixes up the props, the return type we have yet to manage.
I'm yet another person who tried to fix the Functional Component types of react to be more open (and reflect what react can actually handle) only to be met with cryptic errors because of this issue ^^
this should compile indeed:

Yeah, unfortunately right now when returning children you need to cast them to as JSX.Element | null 😢
@Kovensky or self-recursive array, or string, or number, I believe, as of react 16.