Skip to content
This repository was archived by the owner on Jan 22, 2020. It is now read-only.

Commit f26a1b6

Browse files
committed
Merge pull request #124 from paypal/v3.x
[v3.x] - support react-router@1 and react@0.14
2 parents 05ab97e + fa82bb6 commit f26a1b6

25 files changed

Lines changed: 461 additions & 200 deletions

README.md

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
### Install
1212
```sh
13-
# In your express app, react-engine needs to be installed along side react and optionally react-router
14-
npm install react-engine@2 react@0.13 react-router@0.13 --save
13+
# In your express app, react-engine needs to be installed along
14+
# side react and optionally react-router (+ history, react-router's dependency)
15+
npm install react-engine react react-router history --save
1516
```
1617

1718
### Usage On Server Side
@@ -75,13 +76,13 @@ The options object can contain properties from [react router's create configurat
7576

7677
Additionally, it can contain the following **optional** properties,
7778

78-
- `docType`: _String_ - a string that can be used as a doctype (_Default: `<!DOCTYPE html>`_).
79-
docType might not make sense if you are rendering partials/sub page components, in that case, you should pass an empty string as docType.
80-
- `routesFilePath`: _String_ - path for the file that contains the react router routes.
79+
- `docType`: \<String> - a string that can be used as a doctype (_Default: `<!DOCTYPE html>`_).
80+
(docType might not make sense if you are rendering partials/sub page components, in that case you can pass an empty string as docType)
81+
- `routesFilePath`: \<String> - path for the file that contains the react router routes.
8182
react-engine uses this behind the scenes to reload the routes file in
8283
cases where [express's app property](http://expressjs.com/api.html#app.set) `view cache` is false, this way you don't need to restart the server every time a change is made in the view files or routes file.
83-
- `renderOptionsKeysToFilter`: _Array_ - an array of keys that need to be filtered out from the data object that gets fed into the react component for rendering. [more info](#data-for-component-rendering)
84-
- `performanceCollector`: _Function_ - to collects [perf stats](#performance-profiling)
84+
- `renderOptionsKeysToFilter`: \<Array> - an array of keys that need to be filtered out from the data object that gets fed into the react component for rendering. [more info](#data-for-component-rendering)
85+
- `performanceCollector`: \<Function> - to collects [perf stats](#performance-profiling)
8586

8687
###### Rendering views on server side
8788
```js
@@ -150,6 +151,43 @@ Note: By default, the following three keys are always filtered out from `renderO
150151
- `enrouten`
151152
- `_locals`
152153
154+
### Handling redirects and route not found errors on the server side
155+
While using react-router, it matches the url to a component based on the app's defined routes. react-engine captures the redirects and not-found cases that are encountered while trying to run the react-router's [match function on the server side](https://github.com/rackt/react-router/blob/5590516ec228765cbb176c81fb15fe1d4662e475/docs/guides/advanced/ServerRendering.md).
156+
157+
To handle the above during the lifecycle of a request, add an error type check in your express error middleware. The following are the three types of error that get thrown by react-engine:
158+
159+
Error Type | Description
160+
-------------------- | --------------------------------------------------------
161+
MATCH_REDIRECT** | indicates that the url matched to a redirection
162+
MATCH_NOT_FOUND | indicates that the url did not match to any component
163+
MATCH_INTERNAL_ERROR | indicates that react-router encountered an internal error
164+
165+
_** for MATCH_REDIRECT error, `redirectLocation` property of the err has the new redirection location_
166+
167+
```javascript
168+
// example express error middleware
169+
app.use(function(err, req, res, next) {
170+
console.error(err);
171+
172+
// http://expressjs.com/en/guide/error-handling.html
173+
if (res.headersSent) {
174+
return next(err);
175+
}
176+
177+
if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_REDIRECT) {
178+
return res.redirect(302, err.redirectLocation);
179+
}
180+
else if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_NOT_FOUND) {
181+
return res.status(404).send('Route Not Found!');
182+
}
183+
else {
184+
// for ReactEngine.reactRouterServerErrors.MATCH_INTERNAL_ERROR or
185+
// any other error we just send the error message back
186+
return res.status(500).send(err.message);
187+
}
188+
});
189+
```
190+
153191
### Yeoman Generator
154192
There is a Yeoman generator available to create a new express or KrakenJS application which uses react-engine:
155193
[generator-react-engine](https://www.npmjs.com/package/generator-react-engine).
@@ -174,7 +212,7 @@ function collector(stats) {
174212
}
175213

176214
var engine = require('react-engine').server.create({
177-
reactRoutes: './routes.jsx'
215+
routes: './routes.jsx'
178216
performanceCollector: collector
179217
});
180218
```
@@ -185,6 +223,10 @@ var engine = require('react-engine').server.create({
185223
* You can use `js` as the engine if you decide not to write your react views in `jsx`.
186224
* [Blog on react-engine](https://www.paypal-engineering.com/2015/04/27/isomorphic-react-apps-with-react-engine/)
187225
226+
### Migration from 2.x to 3.x
227+
While upgrading to 3.x version of react-engine, make sure to follow the [react-router's 1.x upgrade guide](https://github.com/rackt/react-router/blob/5590516ec228765cbb176c81fb15fe1d4662e475/upgrade-guides/v1.0.0.md) to upgrade react-router related code in your app.
228+
Then, add to your express error middleware, react-engine's MATCH_REDIRECT and MATCH_NOT_FOUND checks.
229+
188230
### Migration from 1.x to 2.x
189231
2.x version of react-engine brought in a major api change. Basically it affects the property names of the [object that gets passed in during the engine creation](https://github.com/paypal/react-engine#server-options-spec) on the server side and also how routes definition is passed into react-engine.
190232

example/Readme.md

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ This movie catalog app illustrates the usage of react-engine to build and run an
33

44
## app composition
55
* [express - 4.x](https://github.com/strongloop/express) on the server side
6-
* [react-engine - 2.x](https://github.com/paypal/react-engine) as the express view render engine
7-
* [react - 0.13.x](https://github.com/facebook/react) for building the UI
8-
* [react-router - 0.13.x](https://github.com/rackt/react-router) for UI routing
6+
* [react-engine - 3.x](https://github.com/paypal/react-engine) as the express view render engine
7+
* [react - 0.14.x](https://github.com/facebook/react) for building the UI
8+
* [react-router - 1.x](https://github.com/rackt/react-router) for UI routing
99
* [webpack - 1.x](https://github.com/webpack/webpack) as the client side module loader
1010
* [babel - 6.x](https://github.com/babel/babel) for compiling the ES6/JSX code
1111

@@ -26,8 +26,8 @@ $ open http://localhost:3000
2626
# (fill out the needed information like name, author, etc..)
2727
$ npm init
2828

29-
# install express, react, react-router & react-engine
30-
$ npm install express react-engine@2 react@0.13 react-router@0.13 --save
29+
# install express, react, react-router (+ history, its dependency) & react-engine
30+
$ npm install express react-engine react react-router history --save
3131

3232
# install the rest of the dependencies
3333
$ npm install babel-register babel-preset-react webpack --save
@@ -70,10 +70,13 @@ $ open http://localhost:3000
7070
var DetailPage = require('./views/detail.jsx');
7171

7272
var routes = module.exports = (
73-
<Router.Route path='/' handler={Layout}>
74-
<Router.DefaultRoute name='list' handler={ListPage} />
75-
<Router.Route name='detail' path='/:id' handler={DetailPage} />
76-
</Router.Route>
73+
<Router>
74+
<Route path='/' component={Layout}>
75+
<IndexRoute component={ListPage} />
76+
<Route path='/movie/:id' component={DetailPage} />
77+
<Redirect from='/gohome' to='/' />
78+
</Route>
79+
</Router>
7780
);
7881
```
7982

@@ -96,17 +99,19 @@ $ open http://localhost:3000
9699
<head>
97100
<meta charSet='utf-8' />
98101
<title>React Engine Example App</title>
102+
<link rel='stylesheet' href='/styles.css'></link>
99103
</head>
100104
<body>
101105
<div>
102-
{/* Component that renders the active child route handler of a parent route handler component. */}
103-
<Router.RouteHandler {...this.props} />
106+
{/* Router now automatically populates this.props.children of your components based on the active route. https://github.com/rackt/react-router/blob/latest/CHANGES.md#routehandler */}
107+
{this.props.children}
104108
</div>
109+
<script src='/bundle.js'></script>
105110
</body>
106111
</html>
107112
);
108113
}
109-
});
114+
});
110115

111116
// public/views/list.jsx file contains the catalog view elements of our app.
112117
// we iterate through the array of movies and display them on this page.
@@ -119,13 +124,14 @@ $ open http://localhost:3000
119124
<ul>
120125
{this.props.movies.map(function(movie) {
121126
return (
122-
<li>
123-
<Router.Link to='detail' params={{id: movie.id}}>
127+
<li key={movie.id}>
128+
<Router.Link to={'/movie/' + movie.id}>
124129
<img src={movie.image} alt={movie.title} />
125130
</Router.Link>
126131
</li>
127132
);
128133
})}
134+
129135
</ul>
130136
</div>
131137
);
@@ -135,12 +141,12 @@ $ open http://localhost:3000
135141
// public/views/detail.jsx file contains the markup to
136142
// display the detail information of a movie
137143
module.exports = React.createClass({
138-
mixins: [Router.State],
139144
render: function render() {
140-
var movieId = this.getParams().id;
145+
var movieId = this.props.params.id;
141146
var movie = this.props.movies.filter(function(_movie) {
142147
return _movie.id === movieId;
143148
})[0];
149+
144150
return (
145151
<div id='detail'>
146152
<h1>{movie.title}</h1>
@@ -198,6 +204,28 @@ $ open http://localhost:3000
198204
});
199205
});
200206

207+
// add the error handler middleware
208+
app.use(function(err, req, res, next) {
209+
console.error(err);
210+
211+
// http://expressjs.com/en/guide/error-handling.html
212+
if (res.headersSent) {
213+
return next(err);
214+
}
215+
216+
if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_REDIRECT) {
217+
return res.redirect(302, err.redirectLocation);
218+
}
219+
else if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_NOT_FOUND) {
220+
return res.status(404).send('Route Not Found!');
221+
}
222+
else {
223+
// for ReactEngine.reactRouterServerErrors.MATCH_INTERNAL_ERROR or
224+
// any other error we just send the error message back
225+
return res.status(500).send(err.message);
226+
}
227+
});
228+
201229
// the last step in the server side is to configure the express app to listen on port 3000
202230
app.listen(3000, function() {
203231
console.log('Example app listening at http://localhost:%s', PORT);

example/index.js

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,7 @@
1616
'use strict';
1717

1818
require('babel-register')({
19-
presets: ['react']
19+
presets: ['es2015', 'react']
2020
});
2121

22-
var PORT = 3000;
23-
var path = require('path');
24-
var movies = require('./movies.json');
25-
var express = require('express');
26-
var renderer = require('react-engine');
27-
28-
var app = express();
29-
30-
// create the view engine with `react-engine`
31-
var reactRoutesFilePath = path.join(__dirname + '/public/routes.jsx');
32-
33-
var engine = renderer.server.create({
34-
routes: require(reactRoutesFilePath),
35-
routesFilePath: reactRoutesFilePath
36-
});
37-
38-
// set the engine
39-
app.engine('.jsx', engine);
40-
41-
// set the view directory
42-
app.set('views', path.join(__dirname, '/public/views'));
43-
44-
// set jsx as the view engine
45-
app.set('view engine', 'jsx');
46-
47-
// finally, set the custom view
48-
app.set('view', renderer.expressView);
49-
50-
// expose public folder as static assets
51-
app.use(express.static(path.join(__dirname, '/public')));
52-
53-
// add the our app routes
54-
app.get('*', function(req, res) {
55-
res.render(req.url, {
56-
movies: movies
57-
});
58-
});
59-
60-
var server = app.listen(PORT, function() {
61-
console.log('Example app listening at http://localhost:%s', PORT);
62-
});
22+
require('./server');

example/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
"dependencies": {
1010
"babel-core": "^6.3.17",
1111
"babel-loader": "^6.2.0",
12+
"babel-preset-es2015": "^6.3.13",
1213
"babel-preset-react": "^6.3.13",
1314
"babel-register": "^6.3.13",
1415
"express": "^4.13.3",
16+
"history": "^1.17.0",
1517
"json-loader": "^0.5.4",
16-
"react": "^0.13.3",
17-
"react-engine": "^2.6.0",
18-
"react-router": "^0.13.5",
18+
"react": "^0.14.3",
19+
"react-engine": "3.0.0-rc.2",
20+
"react-router": "^1.0.3",
21+
"serve-favicon": "^2.3.0",
1922
"webpack": "^1.12.9"
2023
}
2124
}

example/public/favicon.ico

5.3 KB
Binary file not shown.

example/public/routes.jsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@
1515

1616
'use strict';
1717

18-
// import react and react-router
19-
var React = require('react');
20-
var Router = require('react-router');
18+
import React from 'react';
19+
import { Router, Route, IndexRoute, Redirect } from 'react-router';
2120

22-
var Layout = require('./views/layout.jsx');
23-
var ListPage = require('./views/list.jsx');
24-
var DetailPage = require('./views/detail.jsx');
21+
import Layout from './views/layout.jsx';
22+
import ListPage from './views/list.jsx';
23+
import DetailPage from './views/detail.jsx';
2524

2625
var routes = module.exports = (
27-
<Router.Route path='/' handler={Layout}>
28-
<Router.DefaultRoute name='list' handler={ListPage} />
29-
<Router.Route name='detail' path='/:id' handler={DetailPage} />
30-
</Router.Route>
26+
<Router>
27+
<Route path='/' component={Layout}>
28+
<IndexRoute component={ListPage} />
29+
<Route path='/movie/:id' component={DetailPage} />
30+
<Redirect from='/gohome' to='/' />
31+
</Route>
32+
</Router>
3133
);

example/public/views/detail.jsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
'use strict';
1717

1818
var React = require('react');
19-
var Router = require('react-router');
2019

2120
module.exports = React.createClass({
2221

23-
mixins: [Router.State],
24-
2522
render: function render() {
26-
var movieId = this.getParams().id;
23+
var movieId = this.props.params.id;
2724
var movie = this.props.movies.filter(function(_movie) {
2825
return _movie.id === movieId;
2926
})[0];

example/public/views/layout.jsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
'use strict';
1717

1818
var React = require('react');
19-
var Router = require('react-router');
2019

2120
module.exports = React.createClass({
2221

@@ -31,11 +30,11 @@ module.exports = React.createClass({
3130
</head>
3231
<body>
3332
<div>
34-
{/* Component that renders the active child route handler of a parent route handler component. */}
35-
<Router.RouteHandler {...this.props} />
33+
{/* Router now automatically populates this.props.children of your components based on the active route. https://github.com/rackt/react-router/blob/latest/CHANGES.md#routehandler */}
34+
{this.props.children}
3635
</div>
36+
<script src='/bundle.js'></script>
3737
</body>
38-
<script src='/bundle.js'></script>
3938
</html>
4039
);
4140
}

example/public/views/list.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ module.exports = React.createClass({
2929
<ul>
3030
{this.props.movies.map(function(movie) {
3131
return (
32-
<li>
33-
<Router.Link to='detail' params={{id: movie.id}}>
32+
<li key={movie.id}>
33+
<Router.Link to={'/movie/' + movie.id}>
3434
<img src={movie.image} alt={movie.title} />
3535
</Router.Link>
3636
</li>

0 commit comments

Comments
 (0)