question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Asynchronous render

See original GitHub issue

Rendering in Choo is a completely synchronous process, thought this makes things way simpler there are a few downsides from 2 perspectives:

  • CPU - Rendering complex components might block the main loop affecting the overall experience
  • I/O - It’s not possible to make the render wait for asynchronous I/O events such as data loading

I believe the later is the most relevant when it comes to SSR. One of the biggest issues of sync SSR is related to data loading and code-splitting. There are several workaround strategies to deal with async I/O on a sync rendering process, most of them require a first-pass render to load or collect something you can wait on and signal when the data is available to read synchronously. Besides being a waste of CPU cycles it’s also not very elegant and adds unnecessary complexity to the code. One could just not have SSR but being able to have truly isomorphic or universal app is a requirement for most content-driven apps.

When I think of async rendering I think of composing the view using sync or async components and then one of two things can happen:

  • Wait until all the components are resolved and then apply node diffing
  • Apply node diffing as components resolve

Ideally both scenarios should be allowed, giving that choice to the developer, but I believe the first one is easier to start with given the current Choo architecture. Overall it should look somehow like this:

const h = require('choo/html')

const syncComponent = (state, emit) => h`
  <p>${state.label}</p>
`

const asyncComponent = async (state, emit) => {
  const data = await asyncDataFetch(state.id)
  return h`
    <p>${data}</p>
  `
}

const view = (state, emit) => h` 
  <div>
    ${syncComponent(state, emit)}
    ${asyncComponent(state, emit)}
  </div>
`

Given that there are a few necessary steps:

  1. Make _prerender function on Choo await for the promise returned by the render to resolve
  2. Make bel/nanohtml also accept promises
  3. Make mount start, toString methods on Choo async since they rely on _prerender

There are a few open-ended questions at the moment:

  1. Error handling in general
  2. What should happen when a render event is triggered within a render call
  3. Possibly others and I would like to open that discussion to the community

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:3
  • Comments:16 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
s3ththompsoncommented, Mar 16, 2018

Had a conversation with @yoshuawuyts about this last weekend. In general I think that making views asynchronous in order to support remote data fetching is a bit of a red herring. IMO data fetching should be represented by an explicit loading state in the app, thereby keeping rendering an immediate, pure, and idempotent representation of the state object. Initially I was in favor of keeping views synchronous in order to recommend this pattern. (I threw together nanofetcher to help manage data fetching as a separate part of the component lifecycle.)

However, @yoshuawuyts convinced me there were a few benefits of asynchronous rendering:

  • moving to an API which could eventually support lit-html was a worthy endeavor in-and-of-itself.
  • making lazy loading of views easier

Still: I think there’s a distinction between making rendering asynchronous (which makes sense for the above reasons) vs. encouraging data fetching and/or initialization to happen as part of the render (which I think should be discouraged in favor of separate component lifecycle events)

Edit: apparently I was missing the simple way of stating all of this:

no side-effects in render phase.

1reaction
toddselfcommented, Mar 15, 2018

I still posit you have everything you need at this point to do this without making the view load the data directly:

With your approach you need to declare your routes twice, in Choo router and Express. My approach only uses Choo router:

const server = require('express')
server.get('*', (req, res) => {
  const state = {}
  state[component][id] = await getDataAsync(req.url)
  const html = await app.toString(req.url, state)
  // ...
})

All of the information you need about the request you’re processing is there – query string params, route info, etc. There is nothing at all that makes this pattern not work from the information you’ve shown me.

I believe your example won’t render the data on the server. There is only one render pass on the server so what will happen is something like:

It does (I’m using this literally right now). The server never fires the get:data call because I’ve pre-populated it on the state object before passing into .toString:

  const data = await getDataAsync()
  const state = {
    somethingIneed: data
  }
  res.send(fe.toString('/', state))
function view (state, emit) {
  if (!state.somethingIneed) {
    emit('get:somethingIneed')
  }
  return html`<div>${state.somethingIneed || ''}</div>`
}

It only tries to get the data if the object doesn’t exist on the state. In the original example, that key gets populated before the state object is passed to choo.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Update on Async Rendering – React Blog
Update on Async Rendering. March 27, 2018 by Brian Vaughn. For over a year, the React team has been working to implement asynchronous...
Read more >
React Suspense: Async rendering in React - LogRocket Blog
With Suspense, you have the ability to suspend component rendering while async data is being loaded. You can pause any state update until...
Read more >
Asynchronous rendering with React - Maxime Heckel's Blog
As of today, when we want to render a component that shows some data coming from an asynchronous call in React, we're stuck...
Read more >
Reactjs async rendering of components - Stack Overflow
The basic example of async rendering of components is below: import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes ...
Read more >
Asynchronous rendering with useDeferredValue | by Nicolas Li
React rendering is synchronous. Whenever a component's state changes, React triggers a re-render of the latter and all of its children. Three steps...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found