Skip to content
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

Component hydration #34

Closed
xkxx opened this issue Apr 18, 2020 · 8 comments
Closed

Component hydration #34

xkxx opened this issue Apr 18, 2020 · 8 comments
Labels
enhancement New feature or request
Milestone

Comments

@xkxx
Copy link

xkxx commented Apr 18, 2020

(First of all, thanks for the great work on making this!)

I'm fascinated by the approach crank takes of modeling component states as local variables. How does it handle when a component instance needs to be discarded and re-created with states intact?

In a hypothetical server-side rendering scenario, the server would create the initial render and send the rendered HTML and component states to the client. The client replicates the initial render by creating new component instances and injecting component states. In both class-style and hook-style React, this is easy because class properties can be re-assigned and useState() acts as a DI mechanism. However, in crank components are impure and may have local variables, so the component states are implicit and harder to track by the framework.

Have you thought about how crank would handle this?

@benjamingr
Copy link

I just learned about this project so taking a stab:

In both React and crank components hold state and are impure - in crank that state is managed by the language itself (through generators) rather than reinventing the wheel (through array pushing and popping in hooks or through instance state with "fake classes" in classes).

Holding the state as local variables means you can use tooling the language makes available to you instead of rolling something "specific to SSR" kind of how async functions with MobX are easier than Redux because they leverage language tooling.

This means SSR is as simple as just calling .render on a component. Components work the same way but the generator only runs as long as you want.

Taking the code example of an async component:

async function QuoteOfTheDay() {
  const res = await fetch("https://favqs.com/api/qotd");
  const {quote} = await res.json();
  return (
    <p>
      “{quote.body}” – <a href={quote.url}>{quote.author}</a>
    </p>
  );
}

In order to SSR this - you just run it as is (with isomorphic-fetch) or if you want to cache the result or otherwise treat it differently on the server - you just inject the server parts and mock them on the server side.

@benjamingr
Copy link

That is, even in regular react SSR is slightly different because components are "less stateful", managing that state in regular JavaScript and just discarding the generators makes things easier.

@yurynix
Copy link

yurynix commented Apr 19, 2020

@benjamingr
The main problem I see, is not how to render the components, IMO, that's already done in the HTML renderer.
The problem is IMO, how to attach event handlers and "forward" the component's generator function to the proper "step" on client side after we got the HTML string from the server.

@benjamingr
Copy link

Oh I see what you're talking about now - that depends on how the stores and state is implemented. I'm not sure if the framework already supports hydration - but hydration would require "cooperation" from the stores in order to "progress the generator automatically" on the client.

@workingjubilee
Copy link

Cross-quoting from Reddit:

Yeah! Right now, you can render components to strings using the HTML renderer, and it works with stateful/async components out of the box! These are the early days of this library, and I plan on creating some kind of meta-framework like Next.js or Svelte’s Sapper, if someone doesn’t do it for me. I looked into creating one, but they almost always involve some kind of advanced webpack magic that I don’t really understand yet.
In regards to hydration, this might be harder to implement given the current internals of the renderer, but my current thinking is what even is the point of hydration? Like, React has a seperate function for reusing existing DOM nodes, but if the outputs are the same, why do we care that the DOM nodes are reused versus blowing away those nodes and rerendering the same thing on the client? I know that in React, server/client rendering mismatches are kind of annoying, so I think I may experiment with the blow it all away and don’t care about mismatches approach.

—Brian Kim

Some responses:

Don’t forget about playing videos. If you return video markup from the server then init the client side, it would interrupt the users experience.

Thank you for the details!
I don't know all the reasons for rehydration but one of the personally more importent ones is: Animations / CSS Transitions will trigger again without the rehydration thingy.

To which he said "excellent points" roughly.

I am not sure that "blow it all away" would not work, however, if the SSR'd rendering framework requested an initial holding state for animations and video, and then loaded in the actual animation/video when it threw the explosives... essentially demanding a clear landing site for the app's dynamic components. Bad for certain kind of pages, ofc, but those pages are often the ones which are most dependent on JS in the first place, so the gains of SSR with hydration (providing a static webpage quickly using a dynamic framework, then enabling a swift transition using the app's diffing powers) were already low.

@marvinhagemeister
Copy link

Blowing away the DOM may work from a technical point of view but is a pretty jarring user experience. It will nearly always lead to white disrupting flash when the DOM is destroyed and build up again.

@botverse
Copy link

To add two more arguments to why I think @marvinhagemeister is right on saying that this will led to a jarring user experience without hydrating:

  1. Recreating the whole DOM tree twice, it's styles, reflows and compositing it's an expensive operation that leads for the browser to have a large impact in the first time to interactive, this is obviously not noticeable in the small example, but very noticeable for the user in a page with 1500 nodes.

  2. Also when the user triggers an event in the pre-rendered dom, it will not translate to the new tree and it's listeners, thing that will work seamlessly in a hydrated React page once the listeners are installed. This will happen quite a lot because the pre-rendered tree might be shown in the screen while the browser is still downloading the whole bundle, processing the javascript and executing it, this process can be as quick as ~0.5 seconds in a desktop with local access to the bundle but as long as few seconds in a mobile.

If the issue goes down to user experience, this is a must that has to be taken into account, otherwise crank will be unlikely to be a choice for anything that needs ssr.

To the point from @yurynix of how to forward the generators to the right step, this can be let to be the responsibility of the architecture of the app, and the libraries that hold the serialized state from the server.

For example if you are driving both the ssr and client renders based on fetch state, you can:

  1. While in ssr and hydrating, in the server you wait for the responses before fulfilling the fetch promise and in the client you give that response immediately when hydrating from the serialized cache.

  2. While reacting to user events in the already hydrated app in the client, you might want to fulfil the fetch promise immediately upon request with the request metadata when there is a cache miss, to leave the app the opportunity to show the loading intermediate component. For this the async pattern you need is a generator, to be able to re-render when the response arrives

@brainkim
Copy link
Member

This was implemented in 0.5.

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

No branches or pull requests

7 participants