This repo is not maintained, I would encourage not using it. Ways of doing similar tasks to this have moved on a great deal since this was relevant.
A simple way to load props and state asynchonously with react-router and redux.
Often pages will have a bunch of resources they need to fetch before rendering
properly. The easy way to do this is add all your fetching logic to the
componentDidMount
method. However this offers a poor user experience as the user has to wait for the page to load and then wait again for the data to be fetched. The best way is to have
everything that's needed already loaded in the initial redux state and passed in as props on the first render.
Define what each component needs with a needs
property.
This should be a function that accepts the router props and then the redux store object. e.g.
MyComponent.needs = (props, store) =>
store.dispatch(doMyAsyncAction())
.then(() => {
var state = store.getState()
return {
myData: state.myAsyncData,
}
})
Why do we pass the store in and not the state? This allows more freedom, you can construct the promise in any way shape or form that makes sense for optimal data loading. If you want to return new props to the component just be sure to return an object at the end of the promise chain.
Any redux actions you dispatch here will also populate your redux store, which means you could use the react-redux
connect
decorator to map your redux state to props on the first render if that's your preferred workflow.
You'll need to configure your server and client. See the next steps below, the steps assume you're familiar with how to setup redux
and react-router
, if you're new to these two packages it might be best to start with their tutorials first.
import { fetchNeeds, AsyncRouterContext } from 'redux-async-props'
// ...
app.get('*', function (req, res, next) {
match({ routes: routes, location: req.url }, (err, redirect, props) => {
const store = createStore(reducer, applyMiddleware(callAPIMiddleware))
fetchNeeds(props, store)
// This param contains the fetched async props for each route
.then((asyncProps) => {
const appHtml = renderToString(
// Ensure you wrap inside a redux Provider as the AsyncRouterContext
// depends on having the redux store in the context.
<Provider store={store}>
{/* We need to use the AsyncRouterContext so it can handle using
* the async props on the initial render. */}
<AsyncRouterContext {...props} asyncProps={asyncProps} />
</Provider>
)
// dump the HTML into a template, lots of ways to do this.
var html = require("raw!./public/index.html")
html = html.replace('<!--__APP_HTML__-->', appHtml)
// Our initialState is slightly different to the norm, instead of being
// made up of only the redux store, it now also contains the async props
// for the initial render (we'll use these on the client to avoid
// rendering twice).
const initialState = {asyncProps, store: store.getState()}
html = html.replace('{/*__INITIAL_STATE__*/}', JSON.stringify(initialState))
res.send(html)
})
})
})
import { AsyncRouterContext } from 'redux-async-props'
//...
const initialState = window.__INITIAL_STATE__
const store = createStore(reducer, initialState.store)
render((
<Provider store={store}>
<Router
routes={routes}
history={browserHistory}
render={(props) => <AsyncRouterContext
{...props}
// Pass in the async props that we're hydrating from
// the server, these are needed so that the initial render
// only needs to be done once.
asyncProps={initialState.asyncProps}
/>}
/>
</Provider>
), document.getElementById('app'))
The client is rendered synchonously so we can't wait for any data to be fetched. This is why we store the initial async props that are generated on the server and then hydrate them for the first render.
Navigating to new pages will execute each new component's needs
. You can
minimise the number of requests sent by checking if your redux state
already contains the data needed. If so don't go and fetch it from the
server again.
Your asynchronous actions have to run on both the client and server.
If your using fetch
then the isomorphic-fetch is a pretty good package for ensuring it works on the server too.
It's safe to use with the connect
decorator as this can just be used as a way
to pre-populate the redux store. Then you can use connect to map the store's
state to props if you so wish.
See the example directory for a full blown example of all the above put into action.