Last active
February 23, 2017 02:22
-
-
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
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
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) | |
} |
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
{ | |
"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" | |
} | |
} |
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
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) | |
} | |
} |
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
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` |
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
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> | |
) |
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
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