Skip to content

React JSX PPX: Support async components #6398

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

Closed
zth opened this issue Sep 11, 2023 · 4 comments · Fixed by #6399
Closed

React JSX PPX: Support async components #6398

zth opened this issue Sep 11, 2023 · 4 comments · Fixed by #6399

Comments

@zth
Copy link
Collaborator

zth commented Sep 11, 2023

With React server components, async React components are now a thing. Async components in this context means that you, on the server, can use an async function (and via that await in the body) as your component function:

let User = async (props) => {
   let userData = await getUserData(props.userId)
   <div>{userData.name}</div>
}

In ReScript it could look like this:

@react.component
let make = async (~userId) => {
  let userData = await getUserData(userId)
  <div> {React.string(userData.name)} </div>
}

However, this doesn't work today: https://rescript-lang.org/try?version=v11.0.0-beta.4&code=AIJwpghgxgLgdFA9gWwA6IHZgzAUAGzBgAJkIBrMYgXmIgGcBPDKYgCgD8BXesEASQAmAShoA+YgG9cxYoRI8+AEQgwINOgHcIASxIBzIgFVeIFWraKBImcQA8gnQDcJkgEqRYcejBA6M+pam5hBwGBDIYMIAvvYA9I4uuNG4QA
(I believe it doesn't work because we're loosing the async context in the PPX as the make function is transformed.)

It is possible to use async components today by not using the PPX, and defining your own wrapping identity function for casting the async component into a regular one:

type userData = {name: string}
let getUserData = async _userId => {name: "test"}

external asyncComponent: ('props => promise<React.element>) => React.component<
  'props,
> = "%identity"

module AsyncComponent = {
  type props = {userId: string}
  let make = asyncComponent(async (props: props) => {
    let userData = await getUserData(props.userId)
    <div> {React.string(userData.name)} </div>
  })
}

let jsx = <AsyncComponent userId="1" />

Playground for the above: https://rescript-lang.org/try?version=v11.0.0-beta.4&code=C4TwDgpgBArgzhATgEQIbFVAvFA3gO1QFsIAuKOYRAS3wHMBfAKABsJgo72BVBFdTDlRwQ+AMZQA+vCQBJACbYAfHkIlyAImARKG5kwgAPbYkIsow0WIDCAeyJhb+CPmDkAFAHIwiW2DjKUD721AgAPABKEKhiwAB0EGwkrkoAlIFRMfFi9o7OrmFMUFDevv4ANEwqOBoApNTyLsDUoBpMTES28jBsUACCIuJ2Dk5N2HhFUKCQQWUBOLgyiArklDT0zMVsHESoANbQQoM2uaOu7pbiUO7B-uS3cOlYKriTW+ywfGgY46gA7qgWpweF8BDc5nElgpUm8oGF5NQAG4vTKxOJrWh0dxLb6oOJqCCpBhwgD0CORkwYMP02ygACs4IZxmEBlZhnkxlD5FgNABGDRQElKJhAA

However, this gives up using the PPX, and requires you to manually wrap the make function in that external. And that doesn't feel very ergonomic.

How can we make this work?

I think this is important to support now that Server Components are becoming a thing, and ReScript supports everything else for them.

Interested in your thoughts here. The way I see it, we need at least 2 things:

  • Figure out why the async context isn't propagated as make is transformed in the PPX
  • Have some sort of functionality like the external above that turns an async component into a regular component that React/JSX expects. Maybe have this builtin to the PPX? Or ship that exact external with rescript-react?

Cc @mununki @cristianoc . What do you think? Interested in your thoughts of this.

@mununki
Copy link
Member

mununki commented Sep 11, 2023

If I remember correctly, making the JSX ppx support async would not be difficult. Rather, I seem to recall that async await was prevented from being used with React components, and I think it would be simple to unlock that and support it.
I'll look into it.

@cometkim
Copy link
Member

The async function is a clear visual signal to the server component. We can make it even clearer like @react.serverComponent.

@mununki
Copy link
Member

mununki commented Sep 12, 2023

I'm asking because I don't have a deep understanding of RSC.
According to the specification, is the async component currently only available in the server component? Is there any room for it to be introduced in the client component?

@cometkim
Copy link
Member

cometkim commented Sep 12, 2023

According to the specification, is the async component currently only available in the server component? Is there any room for it to be introduced in the client component?

Currently, yes, due to the technical limitations

Instead, there are experimental APIs called use and cache that are equivalent
(That also means there is way to use asynchronous server components without having to modify them to use async/await)

The React team guide people to prefer to use async/await when possible based on syntactic familiarity, so there is room for change in the future. However, it may related to React's experimental memoization compiler, and I suspect it is a feature that compiles async/await into use & cache.

According to:

Making Server and Client components easy to distinguish at a glance will help avoid an uncanny valley, where developers must constantly spend mental energy discerning which components run in which environment.

Async functions provide a clear visual signal: if a component is written as an async function, that's a Server Component.

Even in the future, if we were able add support for async components on client, we would likely continue to recommend a separation between async components (i.e. those that fetch data) and stateful ones (i.e. those that use Hooks) to encourage easy refactoring. A common workflow might be to refactor a single component that both fetches data and uses state into multiple components that handle fetching and state separately — then, go one step further and move the fetching to the server.

https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#avoiding-an-uncanny-valley-between-server-and-client

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants