React Resolver
Async-rendering & data-fetching for universal React applications.
React Resolver let's you define data requirements per-component
and will handle the nested, async rendering on both the server & client for you.
Why?
React's rendering is synchronous by design.
As a result, rendering on the server is left as an exercise for the rest of the
community to figure out.
There are some cludgy solutions for this, most of which require having
fat handlers components controllers at the top of your application
that are responsible for marshalling data for all components under them.
For a non-trivial application, this means mixing concerns between your
specialized components and the controllers, which is conceptually difficult
and programmatically annoying to maintain.
How?
The simplest example is a pure, client-side only application. Afterwards,
we can change a few lines to turn this into a universal application.
If you're the visual type, view the examples:
Dependencies
For browsers that don't natively support Promises, use ES6 Promises.
1. Install react-resolver
$ npm install --save react-resolver
2. Decorate Components with Data
Suppose you have the following Stargazer
component to render a Github user's
profile:
export default class Stargazer extends React.Component {
static propTypes = {
user: React.PropTypes.object.isRequired,
}
render() {
}
}
In 2014, you would use componentWillMount
to fire off an AJAX request for
the profile, then use setState
to trigger a re-render with the data.
This won't work on the server-side, and it's annoying to test.
According to most universal boilerplates, we'd put static fetchData()
function
in our component for a middleware or library to handle, rendering the component
when data comes back.
This only works for fat controllers at the top of your application,
usually defined by a React Router <Route>
.
Instead, let's decorate it:
import { resolve } from "react-resolver";
@resolve("user", function(props) {
return axios
.get(`https://api.github.com/users/${props.params.user}`)
.then((response) => response.data)
;
})
export default class Stargazer extends React.Component {
Or, if ES7 decorators aren't your bag:
class Stargazer extends React.Component {
...
}
export default resolve(Stargazer)("user", function(props) { ... });
3. Render on the Client
Again, if we're only rendering on the client, we can render like normal:
import React from "react";
Router.run(routes, location, (Root) => {
React.render(<Root />, document.getElementById("app"));
});
The End. (Unless you want to see how to build a universal application)
4. Resolve on the Client
React Resolver handles bootstrapping server-rendered markup via
Resolver.render
:
import { Resolver } from "react-resolver";
Router.run(routes, location, (Root) => {
Resolver.render(() => <Root />, document.getElementById("app"));
});
5. Resolve on the Server
The server will look very familiar to the client-side version. The difference
being, Resolver.resolve
will async render the application, fetch all data, &
return a <Resolved />
component ready for React.render
, as well as the
data
needed to bootstrap the client:
import { Resolver } from "react-resolver";
Router.create({ location, routes }).run((Handler, state) => {
Resolver
.resolve(() => <Handler {...state} />)
.then(({ Resolved, data }) => {
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="app">${React.render(<Resolved />)}</div>
<script src="/client.min.js" async defer></script>
<script>
window.__REACT_RESOLVER_PAYLOAD__ = ${JSON.stringify(data)}
</script>
</body>
</html>
`)
})
.catch((error) => res.status(500).send(error))
;
});
Enjoy writing universal apps with clarity around data requirements!
Development
If you'd like to contribute to this project, all you need to do is clone
this project and run:
$ npm install
$ npm test
Authors
Collaboration
If you have questions or issues, please open an issue!