What is the recommended approach to handle data loading while using React and SSR without using Redux?
It’s pretty straightforward with react-router. You use meteor/server-render
's onPageLoad
hook server side to match the route. Load data for matched components. Set a window
variable (window.__INITIAL_STATE__
) and then read that variable client-side and use it when you hydrate
.
Do you have an example repos that show this in action. Would love to check out something more fleshed out.
Redux makes this much easier and you should consider it, but here’s roughly how I would do it without redux.
// SERVER
import { Promise } from 'meteor/promise';
import { onPageLoad } from 'meteor/server-render';
import React from 'react';
import { Helmet } from 'react-helmet';
import { renderToString } from 'react-dom/server';
import StaticRouter from 'react-router-dom/StaticRouter';
import { matchRoutes, renderRoutes } from 'react-router-config';
import routes from './imports/ui/routes';
onPageLoad(async sink => {
const { request: { url, browser }, arch } = sink;
if (arch === 'web.browser') {
const branch = matchRoutes(routes, url.pathname);
const promises = branch.map(({ route, match }) => {
const { fetchData } = route.component;
return fetchData instanceof Function ? fetchData(match) : Promise.resolve(null);
});
const [fetchedData] = await Promise.all(promises);
const context = {};
const content = renderToString(
<StaticRouter location={url} context={context}>
{renderRoutes(routes, fetchedData)}
</StaticRouter>
);
sink.renderIntoElementById('react-root', content);
const helmet = Helmet.renderStatic();
sink.appendToHead(`
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(store.getState())};
</script>
`);
}
});
// CLIENT
import React from 'react';
import { hydrate } from 'react-dom';
import BrowserRouter from 'react-router-dom/BrowserRouter';
import { renderRoutes } from 'react-router-config';
import routes from './imports/ui/routes';
const fetchedData = window.__INITIAL_STORE__;
delete window.__INITIAL_STORE__;
hydrate(
<BrowserRouter>
{renderRoutes(routes, fetchedData)}
</BrowserRouter>,
document.getElementById('react-root'),
);
A component might look like this:
class ExamplePage extends Component {
static async fetchData({ params: { urlParam } }) {
return fetchTheThingUsingAParam(urlParam);
}
...
I’ve asked this question before and got no response so i came up with my own, slightly different solution which I explained here: React SSR data hydration help.
My solution is completely self-contained within any component which uses it, and the hydrated data is silently replaced with the live subscription once the client is loaded.
I created an example repo here: https://github.com/wildhart/viewmodel-react-starter-meteor-ssr
mobix
more chars and more
Appreciate it. I have added Redux and have SSR working now. Thanks!
I was trying your solution which works great up until the point using react-router-config on the server. I get errors for matchRoutes and renderRoutes, can I see how you have your routes configured or do you have a repo for example?
Here is a repo of what I have got so far https://github.com/NicholasEli/Meteor-SSR
export default [{
component: AppLayout,
routes: [{
path: '/',
exact: true,
component: Home,
}, {
path: '/s/:scoreId',
component: Score,
}, {
path: '/admin',
component: AsyncAdmin,
}, {
component: SimpleCenterLogoLayout,
routes: [{
path: '/forgot-password',
component: ForgotPassword,
}, {
path: '/change-password/:token',
component: ChangePassword,
}, {
path: '/privacy-policy',
component: PrivacyPolicy,
}, {
path: '/patreon-link',
component: PatreonLink,
}, {
path: '/patreon-confirm',
component: PatreonConfirm,
}, {
path: '404',
component: NotFound,
}, {
path: '*',
component: NotFound,
}],
}],
}];
Thanks I will organize my routes file to mimic that and see what I come up with. Much appreciated.