react-prepare
allows you to have you deeply nested components with asynchronous dependencies, and have everything just work with server-side rendering.
The typical use-case is when a deeply-nested component needs to have a resource fetched from a remote HTTP server, such as GraphQL or REST API. Since renderToString
is synchronous, when you call it on your app, this component won't render correctly.
One solution is to have a central router at the root of your application that knows exactly what data needs to be fetched before rendering. But this solution doesn't fit the component-based architecture of a typical React app. You want to declare data dependencies at the component level, much like your declare your props.
This is exactly what react-prepare
does: it allows you to declare asynchronous dependencies at the component level, and make them work fine with server-side rendering as well as client-side rendering.
react-prepare
is agnostic and can be used vanilla, but it comes with a tiny helper that makes it extremely easy to use along redux
and react-redux
(see examples below).
Let's assume you have defined an async action creator fetchTodoItems(userName)
which performs HTTP request to your server to retrieve the todo items for a given user and stores the result in your redux state.
Your TodoList
component definition would look like this:
import { dispatched } from 'react-prepare';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { fetchTodoItems } from './actions';
const enhance = compose(
dispatched(({ userName }, dispatch) => dispatch(fetchTodoItems(userName))),
connect(({ todoItems }) => ({ items: todoItems }),
);
const TodoList = ({ items }) => <ul>{items.map((item, key) =>
<li key={key}>{item}</li>
</ul>}</ul>;
export default enhance(TodoList);
And your server-side rendering code would look like this:
import { renderToString } from 'react-dom/server';
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { Provider } from 'react-redux';
import prepare from 'react-prepare';
import reducer from './reducer';
async function serverSideRender(userName) {
const store = createStore(reducer, applyMiddleware(thunkMiddleware));
const app = <Provider store={store}>
<TodoList userName={userName} />
</Provider>;
await prepare(app);
return {
html: renderToString(app),
state: store.getState(),
};
}
Your client could re-use the data fetched during server-side rendering directly, eg. assuming your injected it in window.__APP_STATE__
:
const store = createStore(reducer, JSON.parse(window.__APP_STATE__));
render(<Provider store={store}>
<TodoList userName={userName} />
</Provider>, document.getElementById('app'));
For a complete example of a fully-functional app using react-prepare
in conjunction with redux
, see the react-prepare-todo repository.
Helper to use prepared
more simply if your side effects consists mostly of dispatching redux actions.
In the body of the sideEffect
function, you can use the dispatch
function to dispatch redux actions, typically
requesting data from an asynchronous source (API server, etc.).
For example, let's assume you have defined an async action creator fetchTodoItems(userName)
that fetches the todo-items from a REST API,
and that you are defining a component with a userName
prop. To decorate your component, your code would look like:
class TodoItems extends React.PureComponent { ... }
const DispatchedTodoItems = dispatched(
({ userName }, dispatch) => dispatch(fetchTodoItems(userName))
)(TodoItems);
The decorated component will have the following behavior:
- when server-side rendered using
prepare
,sideEffect
will be run and awaited before the component is rendered; ifsideEffect
throws,prepare
will also throw. - when client-side rendered,
sideEffect
will be called oncomponentDidMount
andcomponentWillReceiveProps
.
opts
is an optional configuration object passed directly to the underlying prepared
decorator (see below).
Decorates Component
so that when prepare
is called, sideEffect
is called (and awaited) before continuing the rendering traversal.
Available opts
is an optional configuration object:
opts.pure
(default:true
): the decorated component extendsPureComponent
instead ofComponent
.opts.componentDidMount
(default:true
): on the client,sideEffect
is called when the component is mounted.opts.componentWillReceiveProps
(default:true
): on the client,sideEffect
is called again whenever the component receive props.
Recursively traverses the element rendering tree and awaits the side effects of components decorated with prepared
(or dispatched
).
It should be used (and await
-ed) before calling renderToString
on the server. If any of the side effects throws, prepare
will also throw.
react-prepare
tries hard to avoid object keys conflicts, but since React isn't very friendly with Symbol
, it uses a special key for its internal use.
The single polluted key in the components key namespace is @__REACT_PREPARE__@
, which shouldn't be an issue.