Skip to content

Support asynchronous server rendering (waiting for data before rendering) #1739

Open
@fdecampredon

Description

@fdecampredon

It would seriously ease the process of building something isomorphic if componentWillMount could return a promise and that react would delay rendering until that promise is resolved. I have seen attempt of doing something like that in react-router and rrouter, however giving this responsibility to each component instead of a router module would make more sense for me.

Activity

sophiebits

sophiebits commented on Jun 24, 2014

@sophiebits
Collaborator

The main reason (I believe) that this doesn't exist already is that on the client side, you basically always want to show some sort of loading indicator instead of deferring rendering. (It would also make the code significantly more complex, but we can probably deal with that.)

fdecampredon

fdecampredon commented on Jun 25, 2014

@fdecampredon
Author

There is 2 cases that I find hard to solve without that:

  • On the server side, if you want to fetch data before rendering you can't delegate data retrieval to components since you don't have the information of which component will be rendered
  • On the client side the first time you mount your application after receiving pre-rendered html, even if you have some sort of cache from data retrieved on the server, you might want to use async method to retrieve those data, and that would prevent react from reusing the html.

react-async solve those problems with fibers, and cache. That do the trick but in my point of view those are just hackish solutions to solve a problem that can only be solved in core.

syranide

syranide commented on Jun 25, 2014

@syranide
Contributor

Color me uninformed on this subject, @fdecampredon say that componentWillMount is async and you don't return anything immediately, what is React supposed to render until there is, nothing? If so, why not just return nothing in render if there is no data yet? (Yeah I get server-side) Also, what should happen if props change before componentWillMount fires?

Personally, it seems like it's wrong to dispatch async requests during componentWillMount unless the component really is an isolated black box and also implements a loading indicator. As far as I understand it, React components should not be mistaken for more conventional OOP instances. In the best case, a React component is tool for visualizing the data in props, if it's interactive then possibly also state. It's a view, not a view and model.

To my ears, that sounds like the problem, React components shouldn't be the ones dispatching the async requests, you fetch all the data and when that data is ready, only then do you call React.renderComponent. Same solution client-side and server-side. You also get the ability to abort with an outcome of your choice if any async request fail.

Feel free to dismiss me if I misunderstood something, but it seems that you're treating React components as view and model, when (it seems) they're meant as just the view.

fdecampredon

fdecampredon commented on Jun 25, 2014

@fdecampredon
Author

Color me uninformed on this subject, @fdecampredon say that componentWillMount is async and you don't return anything immediately, what is React supposed to render until there is, nothing? If so, why not just return nothing in render if there is no data yet? (Yeah I get server-side) Also, what should happen if props change before componentWillMount fires?

I must admit that I did not think about all the cases ^^.
This feature would be useful only the first time we mount the top level component, and on the server, it's true that otherwise In most cast you would want to display a loader indicator.

Personally, it seems like it's wrong to dispatch async requests during componentWillMount unless the component really is an isolated black box and also implements a loading indicator. As far as I understand it, React components should not be mistaken for more conventional OOP instances. In the best case, a React component is tool for visualizing the data in props, if it's interactive then possibly also state. It's a view, not a view and model.

In a way or in other you'll want that a 'top-level' component would be able to retrieve data, like it's done in the Flux sample.
In this sample things are pretty simple because retrieving the list of todo is a synchronous operation, if it was not, and in case of pre-rendering on the server, we would render a first time with no data (and loose the pre-rendered markup from server).

In the case of a simple application with one set of data displayed by one view hierarchy there is still not so much problem, you can preload data and still keep the synchronous property of your store.
Now in case of an application composed of multiple modules that you reuse across your application I would like to be able to treat those modules as separate applications that are able to 'subscribe' on different stores (that would be responsible for fetching data).

Perhaps That I get the things the wrong way but some discussion/sample around the web make me think there is something missing somewhere:

  • In some sample it seems to me that @petehunt tried to achieve something similar.
  • react-nested-router promote some similar mechanism in willTransitionTo, and some discussions make me feel that nobody has come with a proper solution.
  • RRouter also provides some kind of mechanism to prefetch data as the component is being rendered/mounted.
  • and finally react-async like I said earlier.
mjackson

mjackson commented on Jun 26, 2014

@mjackson
Contributor

@fdecampredon To be clear, the purpose of willTransitionTo in react-nested-router is not for loading data, specifically because returning a promise from that method will indeed block rendering new UI, which you don't want to do unless you absolutely have to.

syranide

syranide commented on Jun 26, 2014

@syranide
Contributor

@fdecampredon Everyone is still trying to figure things out, so it wouldn't surprise me if no one has a definitive answer. But I'm guessing the devs at Facebook must've run into this themselves a few times.

tobice

tobice commented on Oct 2, 2014

@tobice

Any update on this? I've just started exploring React and I immediately run into this. Many people recommend React as a go to solution for building isomorphic apps, but as long as this is not solved I think it simply can't get the job done.

To my ears, that sounds like the problem, React components shouldn't be the ones dispatching the async requests, you fetch all the data and when that data is ready, only then do you call React.renderComponent. Same solution client-side and server-side. You also get the ability to abort with an outcome of your choice if any async request fail.

Feel free to dismiss me if I misunderstood something, but it seems that you're treating React components as view and model, when (it seems) they're meant as just the view.

If this is true then React is nothing more than a slightly different templating solution / view layer. And that would be a shame because there is such a potential. I really understand @fdecampredon when he mentions those complex applications composed of multiple modules. React would be perfect for this.

I don't think that this approach would mean treating a component as view and model. If you look at the Flux architecture, they think of React components as of controller-views that not only display data (from stores) but also invoke actions based on user interactions. And the actions then update the stores (= model). To me it sounds just like an obvious MVC architecture.

The problem is with the initial action that populates the store. It's fairly simple on the client side where the action can be invoked from the componentDidMount() method as recommended. On the server side on the other hand, we really need some special place and some kind mechanism that would delay the rendering until the action is finished and the stores are populated.

At this moment, it seems to me that the only way is React-async/Fibers + Flux. The nice thing about Flux is that we don't need some artificial cache to transport component states from the server to the client (like it's done in the original react-async example), we can simply initialize the stores and then send them to the client with the html markup (see this example).

But this solution is indeed hackish.

mridgway

mridgway commented on Oct 30, 2014

@mridgway
Contributor

I don't think it necessarily needs to be componentWillMount that is async; I'm not even sure it needs to be a lifecycle event. The real issue is that on the server side there is no way to analyze the component tree until after everything has been rendered to a string.

My ideal solution to solve this would be to allow "rendering" that would just build the component tree, then we would be able to traverse the tree to find components that need additional data, allow us to asynchronously load more data and "re-render" that components subtree, and then once we are ready for flushing the markup, allow us to convert that tree to a string.

This replicates what we're able to do in the browser: have a virtual DOM that we can re-render as we want. The difference is that in the browser DOM updates can be implicit. On the server we need to be explicit about when we render to a string so that we can perform updates to the virtual DOM based on async data.

koistya

koistya commented on Nov 11, 2014

@koistya
Contributor
My ideal solution to solve this would be to allow "rendering" that would just build the component tree, then we would be able to traverse the tree to find components that need additional data, allow us to asynchronously load more data and "re-render" that components subtree, and then once we are ready for flushing the markup, allow us to convert that tree to a string. — @mridgway

Yep, that would be good. Currently, rendering component twice on a server-side can be used as a workaround.

mridgway

mridgway commented on Feb 27, 2015

@mridgway
Contributor

I want to reference react-nexus as an example of what I'd want to be supported within React. It's essentially a rewrite of how mountComponent works except that it builds out the component tree without actually mounting it to the DOM or writing out a string. This allows you to traverse the component tree and fire off asynchronous methods while the tree is being built up. The issue with this implementation as it is, is that it throws away that first tree and then calls React.renderToString anyway, whereas it would be nice to take that pre-render tree and render/mount it.

I'm willing to work on this within core, but would need some pointers. I think one of the requirements is making ReactContext not globally referenced so that async wouldn't cause issues. Are there other globals that might run into issues with this as well?

gaearon

gaearon commented on Feb 27, 2015

@gaearon
Collaborator

@mridgway If I'm not mistaken global ReactContext might be a compat thing and will go away in 0.14. But I might be wrong.

mridgway

mridgway commented on Feb 27, 2015

@mridgway
Contributor

@gaearon Yeah, that's the sense I got from the withContext deprecation.

158 remaining items

gaearon

gaearon commented on Nov 29, 2018

@gaearon
Collaborator
steve-taylor

steve-taylor commented on Nov 29, 2018

@steve-taylor

Awesome @gaearon!

@davnicwil react-baconjs supports unlimited depth.

ivan7237d

ivan7237d commented on Dec 10, 2018

@ivan7237d

@gaearon I wonder if this would make possible support for observables in create-subscription, so that I can convert an observable firing JSX into a react component?

I'd love to have this feature for two reasons. First, in my app the state is stored in a web worker. So while retrieving bits of that state which are required by the component is an async operation, it takes like 5ms and it doesn't make sense for React to render anything while waiting for the data. Second, right now there's no universal way to convert an observable to a component: if your observable emits the first value synchronously, then what you do is subscribe to get that value, then unsub, then subscribe again to listen for changes (that's the Replay subject example in create-observable docs). And if it emits the first value asynchronously, then you initially render null and listen for changes.

davnicwil

davnicwil commented on Apr 10, 2019

@davnicwil

@steve-taylor react-frontload now also supports unlimited depth of component nesting, with the 1.0.7 release.

Hope this library is useful for some people landing on this thread - if you're looking for an async data loading solution that works on client / server render, with very minimal integration effort, you should check it out.

RyanRoll

RyanRoll commented on Aug 21, 2019

@RyanRoll

We had encountered this problem before as our app was built with React Hooks, and then we created a package react-use-api to solve it, which is a custom hook that fetches API data and supports SSR. I hope the package can help people in need.

However, we are still looking forward to waiting for an official solution, Just like react-frontload says, there's no built-in way to wait around for async data loading to happen once render begins currently.

gaearon

gaearon commented on Dec 21, 2020

@gaearon
Collaborator
wmertens

wmertens commented on Dec 23, 2020

@wmertens
Contributor

Summary of my understanding of Server Components as they relate to this topic:

You'd have an SSR setup that renders a root with Server Components. A request would be rendered and result in a stream of elements, depending on the Suspense mechanism in the Server Components.

The SSR can convert this to HTML and stream it to the client, pausing the stream every time it encounters a placeholder. The end result is SSR only requiring a single pass and providing bytes to the client as soon as they are available 🎉.

As you can see, Server Components are crucial to make this work. This means that if you want your app to work like before, as an SPA without further Server Components calls, you need to return Client Components that get their initial state from the SSR and perform data fetches on state changes.

gaearon

gaearon commented on Mar 24, 2021

@gaearon
Collaborator

Here’s the other part that does the actual streaming SSR (work in progress, not done yet). In this world Server Components is where you do data fetching, and then the new HTML renderer turns that stream into an HTML stream that can progressively render as soon as we have some content — even before all data has loaded.

#20970

gaearon

gaearon commented on Apr 1, 2021

@gaearon
Collaborator

Today, we're announcing React Labs — a new video series with technical deep dives with the React team members. Our first video is a Server Components Architecture Q&A deep dive. We hope you enjoy it! (It's very related to the topic in this thread.)

gaearon

gaearon commented on Jun 9, 2021

@gaearon
Collaborator

Yesterday, we published The Plan for React 18.

tldr:

  • We’ve started work on the React 18 release, which will be our next major version.
  • We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18.
  • We’ve published a React 18 Alpha so that library authors can try it and provide feedback.

React 18 will include a lot of foundational work for Suspense. This includes a brand new streaming server renderer which uses the <Suspense> boundaries to stream HTML and to hydrate the page in chunks, dramatically improving responsiveness.

gaearon

gaearon commented on Mar 29, 2022

@gaearon
Collaborator

React 18 is out with the new streaming renderer.

In principle it already supports asynchronous data fetching (producing rendering results as a stream). However, there are still questions about how exactly to transfer data for hydration to the client. So we shouldn't close this yet. The architecture is there but it's not quite usable yet. (However, if you're a framework author, you might be able to start tinkering with it.)

insaaFusion

insaaFusion commented on Dec 26, 2022

@insaaFusion

Use useLayoutEffect hook

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sophiebits@dantman@wmertens@mjackson@anatomic

        Issue actions

          Support asynchronous server rendering (waiting for data before rendering) · Issue #1739 · facebook/react