-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Support Asynchronous Components #526
Comments
Love the idea, but I can't think of a way to implement this that would work in Preact's core. |
@developit, depending on how you implement .then((value) => {
component.constructor.render = component.render = () => {return value} ;
component.forceUpdate();
})
return h('noscript'); and end with calling |
That's quite similar to how the userland solutions work actually (implemented this today!) |
I'm glad I've found this issue.
Since Some time ago I wanted to solve the data fetching problem with minimum impact to the project and as less API as possible. I found out that the easiest and idiomatic way is to support async function as a component. async function ParentComponent(props) {
const data = await SomeAPI.retrieve();
return <ChildComponent data={data} />;
} I use React in my current project and I came up with the next solution: Worth mentioning, right after I've started using async components I found that a lot of things can be solved just by using language features, no libs or APIs. async function ParentComponent(props) {
try {
const data = await SomeAPI.retrieve();
return <ChildComponent data={data} />;
} catch (error) {
return <details>
<summary>An error occurred while fetching data</summary>
<p>{error.message}</p>
</details>;
}
} After Webpack 2 released, I figured that react-coroutine supports examples/code-splitting/modules/Routes.js#L18-L25 And example of using async generator with examples/code-splitting/modules/Routes.js#L27-L45 Using async generator in particular you can easily combine code splitting with loading spinner and co-located data fetching. It does not require any separate libs for different purposes and APIs, it just works because of async nature. I wish React had async components supported by default and I'm not sure if the team can support this suggestion (since there are already implementations in the user land). Though, I think it is important to have this support since it makes easier for developers to start using code splitting, co-located data fetching and a lot of other possible use cases of async functions/generators. @developit, I want to help to implement this in Preact but I need some help/introduction to the codebase. |
I would like to suggest to add the ability for The Readme file on the example code explains what I tried to do and how to use it. The existing tests on the modified Preact run fine, so the change is backward compatible. I am sorry I am new to Preact, I hoped I could be more helpful. Thanks |
I am guessing here but I imagine that what I need to do is pile up all the promises returned from |
@Satyam It would work the same way as #409 really. That said, I'm not sure I follow what async |
@developit If I am not reading it wrong, the idea of #409 is that, if new data arrives when the components are rendering, interrupt them and start with the new data. What I mean to do is that instead is to hold on until the data is there. Specially in SSR, you only have one chance with |
The key with Paul's PR is as you described: components are progressively instantiated as CPU is available - they won't even be invoked/rendered/anything until the scheduler kicks in. This is useful because it means if your data fetching beats the progressive rendering, your first render will be with the fetched data, so nothing is wasted. If CPU time is available before data comes in, components will be "booted" and can show a UI indicating loading. More generally though, it seems a little bit odd to fetch data on the server and do a synchronous static render, but then also re-fetch that same data on the client to do an asynchronous render that likely produces an identical DOM tree. Most people who do full SSR as you describe also send down the data to be used for hydration, so that components boot into a cached state instead of a loading state. Maybe that would be a simpler approach? The problem with making any of the lifecycle methods truly asynchronous (eg: promise-returning) is that it introduces a very real locking issue - if a (parent) re-render is triggered while a component is in an unbooted state, does it boot? If the DOM is mutated outside the control of Preact before a component has booted - is that left in-place somehow (impossible to know that it happened), or is the now mismatched DOM mutated to match the initial render of the component? These are a couple of the main reasons VDOM libs don't tend to rely on asynchronous lifecycle methods, rather choosing to implement asynchronicity internally within the diffing algorithm. When internally implemented, it can be halted or cancelled. That said, #409 has been a long time coming because I'm trying to integrate a pluggable scheduler into it. That would allow you to write a simple scheduler that asks components when they would like to boot, which I think is a very elegant solution to the issue you laid out. You'd define a static method on your components (something like next.js' |
It might seem a little odd to want to do this, but there are some very practical user benefits from doing so. Given that javascript can be blocking for a number of different reasons (network, parsing, etc.) by providing an initial SSR state that is already valid, you can ensure that the user already has content ready to go regardless of the current state of javascript. In a more real life example, we had an issue with our CDN recently where assets were taking upwards of 5 second to download over a fast wifi connection. Had we not had valid async SSR content delivered, it would have had an adverse impact on our users. It didn't matter that the content eventually got rehydrated with an initial state because it was already valid. |
Isn't that just display content though, with no interactivity? |
Correct. It's a bit of uncanny valley since the time to interactive is still delayed until after scripts, but some content is better than no/placeholder content until after blocking javascript. It's an approach that has unfortunately been left to userland, so there's a lot of haphazard magic to sometimes make it work correctly. And unfortunately I think some people tend to avoid it because of the potential difficulty. Maybe Async SSR is a whole other issue, so I don't want to commandeer this issue, it's just been something on my mind. It's not necessarily something that would need to go into Preact's core, but rather something like |
You might find this thread interesting - it's a super simple full async SSR: |
It is not my intention to do that. As I mention towards the end of this section, a Redux store could be populated and it serialized contents sent to the client, where it can be re-hidrated. Though a developer my blindly query the source for data when invoked, it would be sensible to check whether the data is already there and, if it is already available (from the SSR rendering or whatever) the Promise would be resolved immediately. In the end, whether the developer does this or not is no argument against the feature being available. The examples shown in my sample code are oversimplified, I normally use Redux and hardly ever fetch data blindly. I defer to you regarding the locking and refreshing issues, if that is a show-stopper, then there is no further discussion. I don't mind if existing methods are repurposed as I suggest or a new However, I do prefer to have |
Actually, I think I have a sort of middle ground here. When using When rendering on the client side, it would be acceptable for the render not to be paused. However, it is important that the components get re-rendered with the newly received data. This is no problem when using Redux, as any change in the data store would trigger such a re-render, but there is no Redux in this example to do it. Moreover, it would be important that the re-render be queued once all the requested data arrives, to avoid pointless intermediate states. Stateful components might chain a So, my suggested behaviour is:
|
Given that React now has an official API for loading components lazily (Suspense), perhaps we should close this issue in favour of one for implementing an API compatible with |
Agreed @robertknight. We have support for Suspense-based deferred hydration and tree creation landing in #2291 / #2214 and that will likely be the most logical path forward here. It avoids the pitfalls mentioned in this thread relating to deadlocked async trees, and doesn't rely on the renderer dealing with concurrent asynchronous trees. |
Inspired by https://twitter.com/youyuxi/status/824659082816716802
It'd be great if we could do the same with Preact. Looking forward to a discussion about what would it take to add support. Off the top of my head, this would possibly need
h
to support Promises as childrenA common argument against this would be to have this in userland code, however there are legitimate cases where having this baked into the framework itself would strongly encourage developers to start using code-splitting, which I think fits into Preact's goals of performance and min size.
The text was updated successfully, but these errors were encountered: