Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// routes.js
const routes = [
{
path: '/',
component: Home,
exact: true
},
{
path: '/gists',
component: Gists
},
{
path: '/settings',
component: Settings
}
]
// components
class Home extends React.Component {
// called in the server render, or in cDM
static fetchData(match) {
// going to want `match` in here for params, etc.
return fetch(/*...*/)
}
state = {
// if this is rendered initially we get data from the server render
data: this.props.initialData || null
}
componentDidMount() {
// if rendered initially, we already have data from the server
// but when navigated to in the client, we need to fetch
if (!this.state.data) {
this.constructor.fetchData(this.props.match).then(data => {
this.setState({ data })
})
}
}
// ...
}
// App.js
const App = ({ routes, initialData = [] }) => (
<div>
{routes.map((route, index) => (
// pass in the initialData from the server for this specific route
<Route {...route} initialData={initialData[index]} />
))}
</div>
)
// server.js
import { matchPath } from 'react-router'
handleRequest((req, res) => {
// we'd probably want some recursion here so our routes could have
// child routes like `{ path, component, routes: [ { route, route } ] }`
// and then reduce to the entire branch of matched routes, but for
// illustrative purposes, sticking to a flat route config
const matches = routes.reduce((matches, route) => {
const match = matchPath(req.url, route.path, route)
if (match) {
matches.push({
route,
match,
promise: route.component.fetchData ?
route.component.fetchData(match) : Promise.resolve(null)
})
}
return matches
}, [])
if (matches.length === 0) {
res.status(404)
}
const promises = matches.map((match) => match.promise)
Promise.all(promises).then((...data) => {
const context = {}
const markup = renderToString(
<StaticRouter context={context} location={req.url}>
<App routes={routes} initialData={data}/>
</StaticRouter>
)
if (context.url) {
res.redirect(context.url)
} else {
res.send(`
<!doctype html>
<html>
<div id="root">${markup}</div>
<script>DATA = ${escapeBadStuff(JSON.stringify(data))}</script>
</html>
`)
}
}, (error) => {
handleError(res, error)
})
})
// client.js
render(
<App routes={routes} initialData={window.DATA} />,
document.getElementById('root')
)
@rolele

This comment has been minimized.

Copy link

rolele commented May 16, 2017

Here you are creating your routes using the routes json object that you pass as a props to App.
I am not using a json object to define all my routes, I am using the Route component nested in many different Components in combination with the Link Component.
How can I do server rendering if I do not define this global routes json object?

@matteocng

This comment has been minimized.

Copy link

matteocng commented May 29, 2017

@rolele you would probably have to convert your <Route> structure to a routes array; nesting is supported, see the react-router-config documentation. Then:

  • import routes on the client:

    • pass it to renderRoutes as shown in the react-router-config documentation. This renders the top level routes inside <BrowserRouter>.
  • import routes on the server:

    • const matches = routes.reduce ... in this gist can be replaced with matchRoutes.
  • Keep the Link components inside the React components as usual.

  • Call renderRoutes inside components that have child routes.

@nettosama

This comment has been minimized.

Copy link

nettosama commented Jun 2, 2017

One problem I'm seeing with this code is that if you navigate out of the first route, and come back to it, you will not trigger a new fetch, since it will always catch the data from window.DATA.

@shri3k

This comment has been minimized.

Copy link

shri3k commented Jun 25, 2017

@nettosama You can probably clean up the state of the component in componentWillUnmount if you want the fresh data.

@underbluewaters

This comment has been minimized.

Copy link

underbluewaters commented Jul 11, 2017

The trouble with this approach is that it assumes the same logic for fetching data on the server as the client. On the server you may want to use a database connection or ORM to get what you want, or even satisfy the needs of multiple fetchData calls with the same query. Oh man we're getting into GraphQL/Relay territory now...

@antonybudianto

This comment has been minimized.

Copy link

antonybudianto commented Mar 19, 2018

  static fetchData(match) {

cannot be tree-shaked :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.