-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
RFC: Consult new JSX.ElementType for valid JSX element types #51328
Conversation
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
The TypeScript team hasn't accepted the linked issue #21699. If you can get it accepted, this PR will have a better chance of being reviewed. |
2 similar comments
The TypeScript team hasn't accepted the linked issue #21699. If you can get it accepted, this PR will have a better chance of being reviewed. |
The TypeScript team hasn't accepted the linked issue #21699. If you can get it accepted, this PR will have a better chance of being reviewed. |
Is this entirely true? This allows TypeScript to customize what sorts of things can be used as tag names, but it doesn't allow users to capture the type returned by these functions as part of a JSX invocation. To be clear, I don't have any specific objections to that direction, especially given that there's several backwards-compatibility issues and a huge performance impact involved in doing something of that sort; but I just think we need to be clear about what is/isn't getting fixed. Maybe that's pedantic. 🙂 |
I looked at the problems the issues are describing instead of the specific implementation they're proposing (especially #14729). I have no problem in unlinking these issues (though we can always reopen the issues if people think this PR doesn't fully address their issue). I was more concerned this PR is missed/auto-closed because it didn't have an associated issue. |
From today's design meeting (#51344) we had the following questions:
We also had a suggestion:
|
The backstory for the naming was that we already have Which is also why I copied over the type parameter. My original plan was that the type-checker uses the parameter to infer the props type but that's not implemented and I don't know if we'll ever need it. Especially considering the type parameter is ignored for host components (the
That's a bit tricky because in JSX generally and React specifically "element type" is a pretty well defined term. It's comes naturally from We already have
To do what? Are you planning to lookup the React namespace in the TypeScript compiler? In #21699 specifically it was brought up that any solution should also be concerend with other libraries using JSX (see #21699 (comment)). Using the React namespace would defeat that purpose.
Some comments on the meeting notes #51344:
There have been long standing issues that we can't return every possible type from function components (
It should work. However, class components are already properly typed because we can hook into |
I thought about it and I'm not sure yet we actually want to properly type these. It may even be more useful to make element types fully opaque since you're not supposed to deal with their structure anyway (e.g. calling a function component directly is really bad since it makes assumptions about their implementation). It may make more sense for things that are symbols and these cases can be solved by the proposed |
IMHO the issue is there though. Sure, it's not the end of the world - but those exotic components shouldn't be callable at the type level. The added JSDoc description is nice but it just shouldn't be needed. I get that might be puristic of me - but this is how I see it and from my PoV, it is a problem (that exists at the moment). I get the problem that you are trying to solve here but at the same time, this is an excellent moment to attempt to solve both problems at the same time, together. I would just like to avoid a scenario in which this PR gets merged in without considering this other problem. If eventually this other problem would get solved, it feels quite likely to me (I could be wrong) that the fix for it would make this thing here redundant - and from that perspective, I think that it's at least worth trying to fix both at once now. I agree that the types of those exotic components, or function components, could even be more opaque (although I doubt that you'd like to mandate annotating all function components with some kind of a special type to make them opaque at the declaration sites). But that would still require some type of checker support to be added and for this to be considered in the JSX design in the compiler |
How would that be different from adding correct types for something like
What is the problem that exists today? It sounds like you're saying people calling forwardRef components directly today and running into runtime type issues? Personally I have never seen that while I do see plenty of people running into problems with render return values frequently. This proposal focuses on the common issues I've seen.
Do you have some concrete ideas that you want to share? |
(FWIW the "Why are we revisiting?" is sometimes posed as a motivating question in the design meeting to provide with "what's different also?", but I appreciate that background @eps1lon) |
We could offload the decision/mapping of what is a valid type to the framework. We already have type LibraryAllowedElementTypes<T> = T extends ExoticComponent<infer Props> ? Props : never I'm not saying that this is the exact API that I'm proposing - but something along those lines would probably be an option. To allow what you want to allow here you could expand this to: type LibraryAllowedElementTypes<T> = T extends ReactJSXElementConstructor<
infer Props
>
? Props
: T extends ExoticComponent<infer Props>
? Props
: string; We'd have to figure out the exact rules for how this would fit the existing types in the
I'm not saying that this is a practical problem - it's still a design problem in my opinion though. Clearly, JSX in TypeScript has not been designed to allow arbitrary element types in the past. it was designed for the React needs at the time - and it shows. That's fine but JSX was adopted by more libraries, React evolves, allows more things etc. So I think that this is a great opportunity to rethink how the JSX implementation in TS could become more flexible to allow what you are proposing and fix some old limitations at the same time. |
Are we planning on handling the rendering of Promises here, or should we handle it separately? |
This PR enables handling that. We can't do it without this PR. But we also don't want to do it here. Since JSX is a general concept and there's no spec that libraries using JSX need to be able to render Promises. As far as I know only React server components can. |
@sandersn I'm not sure what's expected of me now. I'm waiting for a reply to #51328 (comment). Unless I misunderstood @DanielRosenwasser and this wasn't a question but a definitive statement:
Or should I implement looking up the |
@DanielRosenwasser Yes, I've been hoping to switch from using tagged template literals (https://github.com/TylorS/typed-fp/blob/development/packages/html/src/tag/Fx.ts#L16) to utilizing JSX to gain an easier path to supporting type-safe properties (without language service plugins and an additional type checker), and access to the JSX AST for compiler optimization, while still being able to aggregate typed resources and typed errors in unions from Effect-ts (formerly fp-ts) to build composable components. For a pretty basic example, https://github.com/TylorS/typed-fp/blob/development/example/pages/layout.ts#L15, the returned type understand that a Link component, and also an outlet, require a Router service to function. |
Anyone know when ElementType will be available in @types/react? |
See DefinitelyTyped/DefinitelyTyped#65135 (and DefinitelyTyped/DefinitelyTyped#65220) |
Plan is to ship once TypeScript 5.1 stable out. May ship after they cut the RC if I get too excited. |
Sorry for a little off the topic, but does anyone know if there are version numbers I can put in my package.json that will allow for this function signature error not to happen in my code. Here is my current package.json I don't need it to be safe for production, just development so I can do screen recordings for a course without having the error showing in VS-Code
|
Hey @pkellner, just use the nightly ( There's also a VS Code nightly extension for TypeScript/JavaScript: https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next |
@DanielRosenwasser I've been trying that and similar and still showing the error (which is a problem for my instructional video I'm doing, as it looks like an error but really isn't). Here are my steps that I think should eliminate the error.
/src/app/page.tsx import MyComp from './my-comp'
export default function Home() {
return <MyComp />
} /src/app/my-comp.tsx export default async function MyComp() {
return <div>MyComp</div>
} Current package.json after update {
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/node": "18.16.3",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"eslint": "8.39.0",
"eslint-config-next": "13.3.4",
"next": "13.3.4",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"typescript": "^5.1.0-dev.20230501"
}
} Error we have always had for async components and still have after @DanielRosenwasser fix |
Per #51328 (comment) and #51328 (comment) above, I do not believe the changes needed in |
Thanks @jakebailey, is there a straight forward way for someone like me (who is good at git, but not a wizard) create my own TypeScript and run it in VS Code? Otherwise, my alternative is to dim the error to almost nothing and hope people don't see it in my upcoming Pluralsight React Server Components course. I can't completely turn off errors because then I don't see them as I'm typing and it will drive me nuts. I have done a ton of docs on docs.microsoft.com so I'm not without skills, but like I said, I'm not a wizard like you folks. |
If you really need it ASAP and aren't concerned about incompleteness, then the easiest way would be to use something like |
My contract is such that I don't release the course until NextJS 13 experimental releases, and I can't imagine that they would release before this is fixed. Even if they do release, I've got at least 6 more weeks of work to do my 3 hour course (I'm an hour done, but mostly with soft demos, no vs-code yet). So, bottom line, I'm gambling a little, but not that much I don't think. I'm counting on 6 plus weeks from now, the code I'm showing will not cause that error when people install nextjs 13, react, and the current TypeScript. I definitely will not release a patchy type process on a course teaching released s/w. Would you mind giving me a few more details on the steps and versions I need to install. I'd really apprecate it. It will get my demo's to look good instead of red squiggles all over the place. Re-recording all that would be really hard. It literally takes me about 1 month per hour of finished video including all the recording and edits (I'm slow, but pretty much that's the same for all the other authors at Pluralsight I respect the most). Thanks again, and sorry for the big ask. it's important to me. |
In short, if you're using react, you have |
Closes #21699
Closes #14729
Enables closing DefinitelyTyped/DefinitelyTyped#18051 and DefinitelyTyped/DefinitelyTyped#18912
Quick terminology intro:
Today, function components that return anything but
null | JSX.Element
are not allowed as element types in React. For example,const Component = () => 42; <Component />
would be rejected by the type-checker because 42 is not a valid JSX element. Note that this return value would be perfectly fine in class components.However, in React, components can return a
ReactNode
. That type includesnumber | string | Iterable<ReactNode> | undefined
(and will likely includePromise<ReactNode>
in the future).Prior attempts at fixing this issue tried to lookup the type of the JSX factory to determine what correct element types are. These attempts were abandoned due to severe perf regressions.
Instead of looking up the type of the JSX factory I'm proposing looking up the type of
JSX.ElementType
. This allows libraries to explicitly define what can be used as an element type. IfJSX.ElementType
does not exist, we fallback to the current behavior.In React,
@types/react
would declareJSX.ElementType
as follows (see tests for concrete usage):Alternatives
A. We already have a dedicated type for JSX class components. Instead of allowing to customize the whole element type, we could just introduce
JSX.ElementFunction
which would work similar toJSX.ElementClass
B. Alias
JSX.Element
toany
benchmarks
Klarna monorepo
Some compat issues uncovered for components being declared as
ForwardRefRenderFunction
. This is fine since you're not supposed to use these as element types but rather pass them toforwardRef
.diff (sample size=1)
reference
baseline