redux-taxi 🚕
ReduxTaxi allows for component-driven asynchronous server-side rendering in isomorphic/universal React+Redux apps.
Use case
When you have asynchronous actions that you want to be processed on the server (at instantiation time, e.g. in the constructor
) before responding to the client, you'll need to explicitly register them to avoid an Error
being thrown. The motivation behind this throw is explained later in this document.
Installation
npm install --save redux-taxi
Usage
Use the decorator to signal at the component level when the component relies on an asynchronous action server-side.
Decorator
/* SomePage.jsx */;;; // explicitly register the async action@// usual redux store connection decorator@ { superprops context; // Dispatch async action thisprops; } // ... render() and other methods
If you're not using ES7 style decorators, you could also call it and export like so:
// ... SOME_ASYNC_ACTIONSomePage
But this is obviously awkward, and ES7 decorators are recommended :)
Asynchronous Action Format
Currently, ReduxTaxi
expects asynchronous actions to have a promise
property attached to them. The PromiseMiddleware
is provided for convenience, but you're free to use your own. ReduxTaxi
detects promises by looking for action.promise
.
TODO: In the future, we may be able to make this configurable by adding a 'promise' check function that can be passed in to do the sniffing/collecting of promises.
/* SomePageActions.js */ ;; { return type: SOME_ASYNC_ACTION promise: api // Promise collected by ReduxTaxi ;}
Server Setup
Since ReduxTaxi
is not opinionated about how your server rendering works, it's left up to you to wire in the functionality.
TODO: This may change as this project grows and if common patterns start to emerge it may be provided for you, but for now it's up to you. Below is an example of how your server-rending might make use of
ReduxTaxi
:
/* server.js */;;; // react-router imports; // redux imports;; // app imports; // Your store configurator const server = ;server;
configureStore
Your configure store just needs to apply the ReduxTaxiMiddleware
with the ReduxTaxi
instance passed in from server.js. Here's a real-world example:
;;;;; // Your app reducers { const middleware = ; return ;}
Motivation
Because ReactDOMServer.renderToString()
is synchronous, we need a way to signal to the server that it should wait for any asynchronous actions to complete before responding to the client. Without this, asynchronous actions will be fired and forgotten on the server, and repeated on the client.
@registerAsyncActions()
decorator?
But why the We wanted the decision to slow the server response down to be a very deliberate decision. To achieve this, we opted for a simple decorator where you can explicitly register which asynchronous actions you want to allow to slow down server rendering. Without explicitly registering your asynchronous actions, a fatal Error
will be thrown.
Ok, but what if I don't want to block the server rendering, but fire the action as soon as the page loads?
Simply put your action dispatching in componentDidMount()
lifecycle method, since this method only executes in a client context. This actually follows Facebook's recommended pattern.
/* SomePage.jsx (Modified to only dispatch async action on client)*/ // (Notice no need to import action type and redux-taxi) @ // This will only execute in a client context { // Dispatch async action thisprops; } // ... render() and other methods
How does it work?
In a nutshell, there's special middleware (ReduxTaxiMiddleware.js
along with PromiseMiddleware.js
) that intercepts all actions that have a promise
defined and checks to see if they're registered with ReduxTaxi.js
(which is provided at the request-level via ReduxTaxiProvider.js
). Then, when rendering on the server, any promises that have been collected will be .all().then()
'd and then the server re-renders the markup and finally responds to the client.
NOTE: This re-rendering (rendering twice) is necessary because React does not currently provide a way to instantiate React Components without actually rendering them to something (e.g. the
constructor
does not get called). To mitigate any (albeit marginal) time cost to this, the re-rendering should only be done when there are actually asynchronous actions that fired. A completely synchronous page should render immediately.
Alternative Projects
- async-props - Data-fetching knowledge lives at the Routing level.
- redial - Data-fetching knowledge lives at the Routing level.
- ground-control - Data-fetching knowledge lives at the Routing level.
- react-transmit - Provides a Relay-inspired API, but with Promises instead of GraphQL queries.
- react-resolver - Similar philosophy to
redux-taxi
(knowledge is confined at the component level), but is mostly usable outside of a Redux architecture.