I am using a static fetchData
method on my Route component...
const mapStateToProps = (state) => ({
posts: state.posts
})
@connect(mapStateToProps)
class Blog extends Component {
static fetchData (dispatch) {
return dispatch(fetchPosts())
}
render () {
return (
<PostsList posts={this.props.posts} />
)
}
}
... and collecting all promises before the initial render on the server side...
match({ routes, location }, (error, redirectLocation, renderProps) => {
const promises = renderProps.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
Promise.all(promises).then(() => {
res.status(200).send(renderView())
})
})
It works fine, the server waits until all my promises are resolved before rendering app.
Now, on my client script, I am doing something similar as on the server...
...
function resolveRoute (props) {
props.components
.filter((component) => component.fetchData)
.map((component) => component.fetchData(store.dispatch))
return <RouterContext {...props} />
}
render((
<Provider store={store}>
<Router
history={browserHistory}
routes={routes}
render={resolveRoute} />
</Provider>
), document.querySelector('#app'))
And it works fine. But, as you may deduct, on the initial page render, the static fetchData
is getting called twice (once on the server and once on the client), and I don't want that.
Is there any suggestions on how to solve this? Recommendations?
The Route component from react-router is public by default but we can build upon it to make it restricted. We can add a restricted prop with a default value of false and use the condition if the user is authenticated and the route is restricted, then we redirect the user back to the Dashboard component.
react-router-dom allows us to navigate through different pages on our app with/without refreshing the entire component. By default, BrowserRouter in react-router-dom will not refresh the entire page.
HashRouter: When we have small client side applications which doesn't need backend we can use HashRouter because when we use hashes in the URL/location bar browser doesn't make a server request. BrowserRouter: When we have big production-ready applications which serve backend, it is recommended to use <BrowserRouter> .
I'm typing this from my phone, so I apologize for the lack of formatting.
For my project, I'm doing something similar to you; I have a static fetchData method, I loop through the components from renderProps and then I call the static method and wait for the promises to resolve.
I then, call get state from my redux store, stringify it, and pass it to my render function on the server so that it can render out an initial state object on the client.
From the client, I just grab that inital state variable and pass it to my redux store. Redux will then handle getting your client store to match the one on the server. From there, you just pass your store to the provider and go on as usual. You shouldn't need to call your static method on the client at all.
For an example of what I said, you can check out my github project as code explains itself. https://github.com/mr-antivirus/riur
Hope that helped!
[Edit] Here is the code!
Client.js
'use strict'
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
import createStore from '../shared/store/createStore';
import routes from '../shared/routes';
const store = createStore(window.__app_data);
const history = browserHistory;
render (
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('content')
)
Server.js
app.use((req, res, next) => {
match({ routes, location:req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).send(err);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
if (!renderProps) {
return next();
}
// Create the redux store.
const store = createStore();
// Retrieve the promises from React Router components that have a fetchData method.
// We use this data to populate our store for server side rendering.
const fetchedData = renderProps.components
.filter(component => component.fetchData)
.map(component => component.fetchData(store, renderProps.params));
// Wait until ALL promises are successful before rendering.
Promise.all(fetchedData)
.then(() => {
const asset = {
javascript: {
main: '/js/bundle.js'
}
};
const appContent = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
)
const isProd = process.env.NODE_ENV !== 'production' ? false : true;
res.send('<!doctype html>' + renderToStaticMarkup(<Html assets={asset} content={appContent} store={store} isProd={isProd} />));
})
.catch((err) => {
// TODO: Perform better error logging.
console.log(err);
});
});
});
RedditContainer.js
class Reddit extends Component {
// Used by the server, ONLY, to fetch data
static fetchData(store) {
const { selectedSubreddit } = store.getState();
return store.dispatch(fetchPosts(selectedSubreddit));
}
// This will be called once on the client
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props;
dispatch(fetchPostsIfNeeded(selectedSubreddit));
}
... Other methods
};
HTML.js
'use strict';
import React, { Component, PropTypes } from 'react';
import ReactDom from 'react-dom';
import Helmet from 'react-helmet';
import serialize from 'serialize-javascript';
export default class Layout extends Component {
static propTypes = {
assets: PropTypes.object,
content: PropTypes.string,
store: PropTypes.object,
isProd: PropTypes.bool
}
render () {
const { assets, content, store, isProd } = this.props;
const head = Helmet.rewind();
const attrs = head.htmlAttributes.toComponent();
return (
<html {...attrs}>
<head>
{head.base.toComponent()}
{head.title.toComponent()}
{head.meta.toComponent()}
{head.link.toComponent()}
{head.script.toComponent()}
<link rel='shortcut icon' href='/favicon.ico' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
</head>
<body>
<div id='content' dangerouslySetInnerHTML={{__html: content}} />
<script dangerouslySetInnerHTML={{__html: `window.__app_data=${serialize(store.getState())}; window.__isProduction=${isProd}`}} charSet='utf-8' />
<script src={assets.javascript.main} charSet='utf-8' />
</body>
</html>
);
}
};
To reiterate...
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