Last active
March 26, 2021 15:23
-
-
Save ryanflorence/efbe562332d4f1cc9331202669763741 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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') | |
) |
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...
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
@nettosama You can probably clean up the state of the component in
componentWillUnmount
if you want the fresh data.