rapscallion
rapscallion copied to clipboard
Implement data-fetching for server rendering
This is more of a question, but would it be possible to implement something like this into Rapscallion? https://github.com/rexhome7326/sync-will-mount
Tested it and it works, lets you await a promise in ComponentWillMount() before continuing render. With React Router 4's new API data fetching is a little more cumbersome, so a lot of people are looking for simpler solutions to server side data fetching.
Something like this (in traverse.js)? Just tried it out and it seems to work. Lifted the code from sync-will-mount, can probably change it to use async/await etc...
function evalComponent (seq, node, context) {
const componentContext = getContext(node.type, context);
if (!(node.type.prototype && node.type.prototype.isReactComponent)) {
const instance = node.type(node.props, componentContext);
const childContext = getChildContext(node.type, instance, context);
traverse(seq, instance, childContext);
return;
}
// eslint-disable-next-line new-cap
const instance = new node.type(node.props, componentContext);
let res = null;
let promise = null;
if (typeof instance.componentWillMount === "function") {
instance.setState = syncSetState;
res = instance.componentWillMount();
}
if (res && res.then) {
promise = res
}
let done = false;
if(promise){
promise.then(() => {done = true;});
} else {
done = true;
}
require('deasync').loopWhile(function(){return !done;});
if (done) {
const childContext = getChildContext(node.type, instance, context);
traverse(seq, instance.render(), childContext);
}
}
Feels like this would be a good candidate to explore optional Rapscallion plugins/middleware. It's a very workflow specific thing to have to have in the main library.
It is non-standard but there is definitely general demand for a feature like this. For example, the apollo client currently traverses a react tree before every render to resolve data dependencies (if that could be done during SSR it would introduce a great performance boost to many graphql apps). Other libraries have introduced functionality to solve the same issue.
I think the approach that is really emerging as a convention within the community is to extend the component contract with getInitialProps
. Both next.js (https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle) and inferno (https://infernojs.org/docs/api/inferno-server) are doing this.
@stubailo @jbaxleyiii might be able to offer some thoughts on this from the perspective of a library author.
Thanks for pointing me to getInitialProps
@djgrant! That's exactly what I was looking for. I'm adding this feature request into the queue.
Oh don't get me wrong - it's a cool feature. Was just questioning if it should be a core feature. Feels very... extend-y. That's usually the domain of plugins, especially when it's not core to the features of a project.
Of course that depends on project goals/architecture choices.
@abritinthebay I'm open to exposing a generic plugin interface. My concern with that, however, is that it will be difficult to implement in a way that does not hurt performance. If you have thoughts on how to accomplish that, I'd be eager to hear them. In any event, the implementation will be as non-specific as we can manage without hurting perf.
True, though I'd argue the same is probably in this case - it's all going to have hot-path impact no matter what.
Yeah I think our approach is something similar to getInitialProps, but you can put it anywhere in the tree. I think it's definitely desirable that SSR is possible without requiring a static mapping of URL -> data fetch.
@divmain @abritinthebay How about allowing a visitor function (something similar to https://github.com/ctrlplusb/react-tree-walker) to be attached to the renderer?
render(<App />)
.attachVisitor(function visitor(element, instance, context) {
if (instance.getInitialProps) {
// returning a promise pauses traversal until promise resolves
return instance.getInitialProps().then(initialProps => {
instance.props = initialProps
})
}
})
.toPromise()
That would provide a flexible generic interface, and common visitors such as getInitialPropsVisitor
could be packaged for developer convenience.
cc. @ctrlplusb
That seems a reasonable approach. I'll look into whether this approach adds any performance overhead. It should be possible to add this without impacting perf for those not using the feature...
Hey all!
My promise-based API is now officially released for react-tree-walker
. Seems to work well, but like @divmain states, it would be interesting to do some performance overhead testing on this.
I also released react-async-bootstrapper
which is an abstraction on top of react-tree-walker
and essentially provides what you guys are after, except it looks for a member called asyncBootstrap
rather than getInitialProps
.
I am using this with great success within react-async-component
, which has been applied to the next
branch of my server side rendering starter kit, react-universally
.
Sorry, that is a mess of links. 😀
Maybe my own attempt at solving this issue can be helpful here: https://github.com/elierotenberg/react-prepare
Instead of hacking the React lifecycle functions, I use a specially marked higher-order component, which is detected during the recursive traversal of the tree. I think separating the preparation step from the rendering step is usually a good idea, and I could implement a stream-friendly API so that it works nice with raspscallion
.
Also, https://github.com/elierotenberg/react-traverse performs deep components output rewriting, using higher order magic.
Sorry for the plugs, but I think this can shed a light on various approaches to tackle this problem.
Nice! Thanks for sharing @elierotenberg.
I think separating the preparation step from the rendering step is usually a good idea
This means traversing the React tree twice. In my experiments that was always slower than resolving async work while rendering.
If we go ahead with a plugin approach (e.g. https://github.com/FormidableLabs/rapscallion/issues/51#issuecomment-287202896) then you could write a plugin that conforms to your projects' APIs.
Will fiber land and change everything?
https://github.com/facebook/react/issues/10294#issuecomment-318184274
🤤
To be clear “asynchronous” Fiber features are not related to server side rendering. They’re related to client-side updates. The new server side renderer is not even using Fiber, and is written as standalone renderer with a simple while
loop.
@gaearon Thanks for the clarification! Is a server side renderer that is capable of doing async work during rendering likely to fall on the React roadmap in the future?
It might. @sebmarkbage was looking into this.