Skip to content

Commit 67f3603

Browse files
committed
Server rendered routes with async data
1 parent 90ddbd7 commit 67f3603

File tree

12 files changed

+191
-66
lines changed

12 files changed

+191
-66
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
presets:['es2015','react', 'stage-0']
3+
}

app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require('babel-register');
12
var express = require('express');
23
var path = require('path');
34
var favicon = require('serve-favicon');
@@ -14,6 +15,7 @@ var app = express();
1415
app.set('views', path.join(__dirname, 'views'));
1516
app.set('view engine', 'jade');
1617

18+
app.locals.pretty = true;
1719
// uncomment after placing your favicon in /public
1820
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
1921
app.use(logger('dev'));

client/app-root.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, {Component} from 'react';
2+
import {Link} from 'react-router';
3+
4+
class AppRoot extends Component {
5+
render() {
6+
return (
7+
<div>
8+
<h2>React Universal App</h2>
9+
<Link to="/home"> Home </Link>
10+
<Link to="/list"> List </Link>
11+
<Link to="/about"> About </Link>
12+
{this.props.children}
13+
</div>
14+
);
15+
}
16+
}
17+
18+
export default AppRoot;

client/app.jsx

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,11 @@
1-
import React, {Component} from 'react';
1+
import React from 'react';
22
import {render} from 'react-dom';
3-
import {Router, browserHistory, Route} from 'react-router';
3+
// import {Router, history, match} from 'react-router';
44

5-
class List extends Component {
6-
constructor(props) {
7-
super(props);
8-
this.state = {items: []};
9-
}
10-
11-
componentDidMount() {
12-
this.fetchList();
13-
}
14-
15-
fetchList() {
16-
fetch('http://jsonplaceholder.typicode.com/users')
17-
.then(res => {
18-
return res.json();
19-
})
20-
.then(data => {
21-
console.log(data);
22-
this.setState({
23-
items: data
24-
});
25-
})
26-
.catch(err => {
27-
console.log(err);
28-
});
29-
}
30-
31-
render() {
32-
return (
33-
<ul>
34-
{this.state.items.map(item => {
35-
return <li key={item.id}>{item.name}</li>;
36-
})}
37-
</ul>
38-
);
39-
}
40-
}
41-
42-
const App = () => {
43-
return (
44-
<div>
45-
<h2>React Universal App</h2>
46-
<List/>
47-
</div>
48-
);
49-
};
50-
51-
const AppRouter = () => {
52-
return (
53-
<Router history={browserHistory}>
54-
<Route path="/" component={App}/>
55-
</Router>
56-
);
57-
};
5+
import AppRouter from './router.jsx';
586

7+
// match({history, routes}, (error, redirectLocation, renderProps) => {
8+
// // render(<Router {...renderProps}/>, document.querySelector('#app'));
9+
// });
5910
render(<AppRouter/>, document.querySelector('#app'));
6011

client/home.jsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
const Home = () => {
4+
return (
5+
<div>
6+
<h1>Home</h1>
7+
</div>
8+
);
9+
};
10+
11+
export default Home;

client/list.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, {Component} from 'react';
2+
3+
class List extends Component {
4+
constructor(props, context) {
5+
super(props, context);
6+
this.state = this.context.data || window.__INITIAL_STATE__ || {items: []};
7+
}
8+
9+
componentDidMount() {
10+
this.fetchList();
11+
}
12+
13+
fetchList() {
14+
fetch('http://jsonplaceholder.typicode.com/users')
15+
.then(res => {
16+
return res.json();
17+
})
18+
.then(data => {
19+
this.setState({
20+
items: data
21+
});
22+
})
23+
.catch(err => {
24+
console.log(err);
25+
});
26+
}
27+
28+
render() {
29+
return (
30+
<ul>
31+
{this.state.items.map(item => {
32+
return <li key={item.id}>{item.name}</li>;
33+
})}
34+
</ul>
35+
);
36+
}
37+
}
38+
39+
List.contextTypes = {
40+
data: React.PropTypes.object
41+
};
42+
43+
export default List;

client/router.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import {Router, browserHistory, Route} from 'react-router';
3+
4+
import AppRoot from './app-root.jsx';
5+
import Home from './home.jsx';
6+
import List from './list.jsx';
7+
8+
const AppRouter = () => {
9+
return (
10+
<Router history={browserHistory}>
11+
<Route path="/" component={AppRoot}>
12+
<Route path="/home" component={Home}/>
13+
<Route path="/list" component={List}/>
14+
</Route>
15+
</Router>
16+
);
17+
};
18+
19+
export default AppRouter;

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
"private": true,
55
"scripts": {
66
"start": "node ./bin/www",
7+
"dev": "DEBUG=server-render/* nodemon ./bin/www",
78
"webpack:server": "webpack --debug --hot --output-path-info --devtool=eval --display-modules --watch -d"
89
},
910
"dependencies": {
11+
"babel-register": "^6.9.0",
1012
"body-parser": "~1.13.2",
1113
"cookie-parser": "~1.3.5",
1214
"debug": "~2.2.0",
@@ -16,6 +18,7 @@
1618
"react": "^15.1.0",
1719
"react-dom": "^15.1.0",
1820
"react-router": "^2.4.1",
21+
"request": "^2.72.0",
1922
"serve-favicon": "~2.3.0"
2023
},
2124
"devDependencies": {
@@ -27,6 +30,7 @@
2730
"babel-polyfill": "^6.7.2",
2831
"babel-preset-es2015": "^6.9.0",
2932
"babel-preset-react": "^6.5.0",
33+
"babel-preset-stage-0": "^6.5.0",
3034
"eslint-config-xo": "^0.14.1",
3135
"eslint-config-xo-react": "^0.7.0",
3236
"eslint-config-xo-space": "^0.13.0",
@@ -40,6 +44,7 @@
4044
"esnext": true,
4145
"space": 2,
4246
"globals": [
47+
"window",
4348
"document",
4449
"fetch"
4550
],

routes/index.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

routes/index.jsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import express from 'express';
2+
import request from 'request';
3+
import React, {Component} from 'react';
4+
import {renderToString} from 'react-dom/server';
5+
import {RouterContext, match, createRoutes} from 'react-router';
6+
7+
import appRouter from '../client/router.jsx';
8+
9+
const routes = createRoutes(appRouter());
10+
11+
class DataProvider extends Component {
12+
getChildContext() {
13+
return {data: this.props.data};
14+
}
15+
render() {
16+
return <RouterContext {...this.props}/>;
17+
}
18+
}
19+
DataProvider.propTypes = {
20+
data: React.PropTypes.object
21+
};
22+
DataProvider.childContextTypes = {
23+
data: React.PropTypes.object
24+
};
25+
26+
/*eslint-disable*/
27+
const router = express.Router();
28+
/*eslint-enable*/
29+
30+
/* GET home page. */
31+
router.get('/', (req, res) => {
32+
match({routes, location: req.url}, (error, redirectLocation, renderProps) => {
33+
if (error) {
34+
res.status(500).send(error.message);
35+
} else if (redirectLocation) {
36+
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
37+
} else if (renderProps) {
38+
const content = renderToString(<RouterContext {...renderProps}/>);
39+
res.render('index', {title: 'Express', content});
40+
} else {
41+
res.status(404).send('Not Found');
42+
}
43+
});
44+
});
45+
46+
router.get('/home', (req, res) => {
47+
match({routes, location: req.url}, (error, redirectLocation, renderProps) => {
48+
if (error) {
49+
res.status(500).send(error.message);
50+
} else if (redirectLocation) {
51+
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
52+
} else if (renderProps) {
53+
const content = renderToString(<RouterContext {...renderProps}/>);
54+
res.render('index', {title: 'Express', content});
55+
} else {
56+
res.status(404).send('Not Found');
57+
}
58+
});
59+
});
60+
router.get('/list', (req, res) => {
61+
match({routes, location: req.url}, (error, redirectLocation, renderProps) => {
62+
if (error) {
63+
res.status(500).send(error.message);
64+
} else if (redirectLocation) {
65+
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
66+
} else if (renderProps) {
67+
request('http://jsonplaceholder.typicode.com/users', (error, response, body) => {
68+
const data = {items: JSON.parse(body)};
69+
const content = renderToString(<DataProvider {...renderProps} data={data}/>);
70+
res.render('index', {title: 'Express', data, content});
71+
});
72+
} else {
73+
res.status(404).send('Not Found');
74+
}
75+
});
76+
});
77+
78+
module.exports = router;

views/index.jade

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extends layout
22

33
block content
4-
h1= title
5-
div#app
4+
script(type='text/javascript').
5+
window.__INITIAL_STATE__ = !{JSON.stringify(data)}
6+
div.container#app!= content

views/layout.jade

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ html
33
head
44
title= title
55
link(rel='stylesheet', href='/stylesheets/style.css')
6+
link(rel="stylesheet", href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css")
67
body
78
block content
9+
script(stype='text/javascript', src="https://code.jquery.com/jquery-2.0.0.js")
10+
script(stype='text/javascript', src="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/js/bootstrap.js")
811
script(type='text/javascript', src='/javascripts/app.js')

0 commit comments

Comments
 (0)