I'm using react-router 2.0 on the server. Some of my top-level route components depend on async data to be fetched and uses Redux to manage state. To deal with this, I'm using a static fetchData()
method to return a Promise.all
of async actions as suggested in the official Redux docs. It works pretty well - the initial render from the server comes with the appropriate data in the Redux store.
My issue comes from how to write these async calls in a way that will work on both the initial render on the server and subsequent renders on the client as different routes get loaded. Currently, I make calls to the static fetchData()
method in my top-level component's componentDidMount()
, but this would cause the fetchData()
to be called once the component mounts on the client. This is what I want when navigating to the route from another, but not on the initial render as we already made this call on the server.
I've been struggling with writing my data fetching logic in a way that:
I was thinking of injecting a prop like { serverRendered: true }
into the initial component but I only have access to the <RouterContext>
when calling renderToString()
. I took a peek at the source for but it doesn’t seem like I can pass the property from that JSX tag either.
I guess my questions would be:
<RouterContext>
so that it passes down to the 'parent' top-level route component? If not in <RouterContext>
, is there another way to inject the property in? fetchData()
so the initial render won't cause fetchData()
to be called on both the server and client?Here’s a code sample of my server.jsx, the fetchData() handling middleware and an example top-level compnent.
I faced up with the same issue. Clearing some global value with setTimeout
or checking Redux store for data existence didn't look good for me.
I decided to check on which paths initial data was loaded, put this array as global value and check matches for route on componentDidMount
.
My server index.js
const pathsWithLoadedData= []; // here I store paths
const promises = matchRoutes(Routes, req.path)
.map(({ route }) => {
if (route.loadData) {
// if route loads data and has path then save it
route.path && pathsWithLoadedData.push(route.path);
return route.loadData(store);
} else {
return Promise.resolve(null);
}
})
My render template
<body>
...
<script>
window.INITIALLY_LOADED_PATHS = ${JSON.stringify(pathsWithLoadedData)};
</script>
...
</body>
Some componentDidMount function
componentDidMount () {
if (!wasFetchedInitially(this.props.route.path)) {
this.props.fetchUsers();
}
}
And helper
export const wasFetchedInitially = componentPath => {
const pathIndex = window.INITIALLY_LOADED_PATHS.indexOf(componentPath);
if (pathIndex < 0) return false;
window.INITIALLY_LOADED_PATHS.splice(pathIndex, 1);
return true;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With