Skip to content

Instantly share code, notes, and snippets.

@natterstefan
Created July 31, 2018 17:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save natterstefan/ff1314d5258727c4f52915bc31c80b6a to your computer and use it in GitHub Desktop.
Save natterstefan/ff1314d5258727c4f52915bc31c80b6a to your computer and use it in GitHub Desktop.
React - Example Component connected to react-router server w/ prefetching data
/* eslint-disable no-underscore-dangle */
import React from 'react'
import PropTypes from 'prop-types'
import get from 'lodash.get'
import BemHelper from 'react-bem-helper'
import { apiClient } from '../../../common/api/client'
// Styling
const classes = new BemHelper('list')
/**
* Example Component, to illustrate server-side prefetching and hydration of data
*
* inspired by (Credits):
* - https://alligator.io/react/react-router-ssr/
*/
export class List extends React.Component {
constructor(props) {
super(props)
if (props.staticContext && props.staticContext.data) {
this.state = {
data: props.staticContext.data,
}
} else {
this.state = {
data: [],
}
}
}
componentDidMount() {
setTimeout(() => {
if (window && get(window, '__PRELOADED_DATA__.posts')) {
this.setState({
data: get(window, '__PRELOADED_DATA__.posts'),
})
delete window.__PRELOADED_DATA__.posts // CAUTION: other components cannot use it anymore
} else {
apiClient('posts').then(data => {
this.setState({
data,
})
})
}
}, 0)
}
render() {
const { data } = this.state
return (
<ul {...classes()}>
{data.length > 0 && data.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
}
List.propTypes = {
staticContext: PropTypes.object, // eslint-disable-line react/forbid-prop-types
}
List.defaultProps = {
staticContext: {},
}
import React from 'react'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import { matchRoutes } from 'react-router-config'
import merge from 'lodash.merge'
import { Routes, ServerRouter } from '../common/routes'
import { createReduxStore } from '../common/createStore'
import { renderFullPage } from './render-full-page'
import { changeCounter } from '../common/actions/app'
import { receivePosts } from '../common/actions/posts'
require('dotenv').config()
const ts = new Date().getTime() // ts at start time
export const handleRender = async (req, res) => {
// cachebuster and redux store
const currentVersion = process.env.VERSION || ts
// Prefetch data based on the current react-router route
// - https://reacttraining.com/react-router/web/guides/server-rendering/data-loading
// - https://alligator.io/react/react-router-ssr/
const matchingRoutes = matchRoutes(Routes, req.path)
const promises = []
matchingRoutes.forEach(route => {
if (route.route.loadData) {
promises.push(route.route.loadData())
}
})
// TODO: create promise-all-never-fails utility (because of one request fails,
// all others would fail/be cancelled as well)
let preloadedData = {}
const result = await Promise.all(promises)
result.forEach(data => {
preloadedData = merge({}, preloadedData, data)
})
// this context is then later accessible in the route component (eg. <App />)
const context = { ...preloadedData }
const { store } = createReduxStore(req.url)
// demonstration of how the store could be manipulated/prepared already on the server
// eg. based on data which is stored in the cookie
store.dispatch(changeCounter(1))
store.dispatch(receivePosts(preloadedData))
// Grab the current state from our Redux store to render it in the html
// https://redux.js.org/recipes/server-rendering#inject-initial-component-html-and-state
const preloadedState = store.getState()
// NOTE:
// one could dispatch an action here now (eg. for instance prepare a state, based
// on the cookies of the user or something else), before renderToString.
// Example: https://github.com/cereallarceny/cra-ssr/blob/master/server/loader.js#L59-L65
const html = renderToString(
<Provider store={store}>
<ServerRouter req={req} context={context} />
</Provider>,
)
// redirect based on the status or when context.url is set (eg. <Redirect />
// component is used)
// Docs:
// - https://reacttraining.com/react-router/web/guides/server-rendering/adding-app-specific-context-information
// - https://alligator.io/react/react-router-ssr/
if (context.status === 404) {
res.status(404)
}
if (context.url) {
return res.redirect(301, context.url)
}
// Send the rendered page back to the client
return renderFullPage({ currentVersion, html, preloadedState, preloadedData })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment