Skip to content

Instantly share code, notes, and snippets.

@gbakernet
Last active February 23, 2017 02:22
Show Gist options
  • Save gbakernet/b1505091ebe9468e667f560c175c5c8a to your computer and use it in GitHub Desktop.
Save gbakernet/b1505091ebe9468e667f560c175c5c8a to your computer and use it in GitHub Desktop.
Thought Experiment using redux-saga to do react server side rendering with express
import { createStore } from "redux"
// Simple Reducer for mock data
export default () => {
const reducer = (state = {}, action) => {
switch (action.type) {
case "DATA_LOADED":
return { heading: `Hello World! ${Date.now()}` }
default:
return state
}
}
return createStore(reducer)
}
{
"name": "express-react-sagas",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "babel-node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.14.1",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.2",
"react-router": "^3.0.2",
"redial": "^0.5.0",
"redux": "^3.6.0",
"redux-saga": "^0.14.3"
},
"babel": {
"presets": [
"env",
"react"
]
},
"devDependencies": {
"babel-cli": "^6.23.0",
"babel-preset-env": "^1.1.8",
"babel-preset-react": "^6.23.0"
}
}
import { call } from "redux-saga/effects"
import { runSaga } from "redux-saga"
import { trigger } from "redial"
import { createMemoryHistory, match as nativeMatch } from "react-router"
import configureStore from "./configureStore"
import renderToString from "./renderToString"
import routes from "./routes"
// Promisify react-router's match function
const match = options => new Promise(resolve =>
nativeMatch(options, (...args) => resolve([...args])),
)
// Generator for triggering redial fetch
function* fetchData(
{ dispatch, getState },
{ params, location, components = [] }
) {
yield call(trigger, "fetch", components, {
location,
params,
dispatch,
getState,
})
}
const sendHtml = (res, code, html) =>
res.status(code).send(html)
// Thought experiment. Handle server requests with a saga
module.exports = function* render(req, res, next) {
const history = createMemoryHistory(req.originalUrl)
const store = configureStore()
const [ error, redirect, renderProps = {} ] = yield call(match, { routes, history })
const notFound = (renderProps.routes || []).find(route => route.path === "*")
if (error) {
yield call(next, error)
return
} else if (redirect) {
yield call([res, res.redirect], 302, `${redirect.pathname}${redirect.search}`)
return
}
try {
yield* fetchData(store, renderProps)
const html = yield call(renderToString, store, renderProps)
yield call(sendHtml, res, notFound ? 404 : 200, html)
} catch (err) {
yield call(next, err)
}
}
import React from "react"
import ReactDOM from "react-dom/server"
import { Provider } from "react-redux"
import { RouterContext } from "react-router"
export default (store, renderProps) => `
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"></head>
<body>
${ReactDOM.renderToString(<Provider store={store}><RouterContext {...renderProps} /></Provider>)}
</body>
</html`
import React from "react"
import { provideHooks } from "redial"
import { connect } from "react-redux"
import { IndexRoute, Route, Redirect } from "react-router"
// Mock Data Fetch
const fetch = ({ dispatch }) => new Promise(resolve =>
setTimeout(() => {
dispatch({ type: "DATA_LOADED" })
resolve()
}, 500)
)
// Top Level Component
export const App = ({ children }) => (
<div className="app">{children}</div>
)
// Route Component
export const Home = ({ heading }) => (
<h1>{heading}</h1>
)
// Route Component
export const NotFound = ({ heading }) => (
<h1>Not Found</h1>
)
export default (
<Route path="/" component={provideHooks({ fetch })(App)}>
<IndexRoute component={connect(state => state)(Home)} />
<Redirect from="/home" to="/" />
<Route path="*" component={NotFound} />
</Route>
)
import express from "express"
import { runSaga } from "redux-saga"
import renderSaga from "./renderSaga"
const app = express()
app.use((req, res, next) => {
runSaga(renderSaga(req, res, next), {
subscribe: () => () => {},
})
})
app.listen(8000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment