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

Commit

Permalink
Merge pull request #124 from paypal/v3.x
Browse files Browse the repository at this point in the history
[v3.x] - support react-router@1 and [email protected]
  • Loading branch information
vuhwang committed Jan 11, 2016
2 parents 05ab97e + fa82bb6 commit f26a1b6
Show file tree
Hide file tree
Showing 25 changed files with 461 additions and 200 deletions.
58 changes: 50 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

### Install
```sh
# In your express app, react-engine needs to be installed along side react and optionally react-router
npm install react-engine@2 [email protected] [email protected] --save
# In your express app, react-engine needs to be installed along
# side react and optionally react-router (+ history, react-router's dependency)
npm install react-engine react react-router history --save
```

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

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

- `docType`: _String_ - a string that can be used as a doctype (_Default: `<!DOCTYPE html>`_).
docType might not make sense if you are rendering partials/sub page components, in that case, you should pass an empty string as docType.
- `routesFilePath`: _String_ - path for the file that contains the react router routes.
- `docType`: \<String> - a string that can be used as a doctype (_Default: `<!DOCTYPE html>`_).
(docType might not make sense if you are rendering partials/sub page components, in that case you can pass an empty string as docType)
- `routesFilePath`: \<String> - path for the file that contains the react router routes.
react-engine uses this behind the scenes to reload the routes file in
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.
- `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)
- `performanceCollector`: _Function_ - to collects [perf stats](#performance-profiling)
- `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)
- `performanceCollector`: \<Function> - to collects [perf stats](#performance-profiling)

###### Rendering views on server side
```js
Expand Down Expand Up @@ -150,6 +151,43 @@ Note: By default, the following three keys are always filtered out from `renderO
- `enrouten`
- `_locals`
### Handling redirects and route not found errors on the server side
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).
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:
Error Type | Description
-------------------- | --------------------------------------------------------
MATCH_REDIRECT** | indicates that the url matched to a redirection
MATCH_NOT_FOUND | indicates that the url did not match to any component
MATCH_INTERNAL_ERROR | indicates that react-router encountered an internal error
_** for MATCH_REDIRECT error, `redirectLocation` property of the err has the new redirection location_
```javascript
// example express error middleware
app.use(function(err, req, res, next) {
console.error(err);

// http://expressjs.com/en/guide/error-handling.html
if (res.headersSent) {
return next(err);
}

if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_REDIRECT) {
return res.redirect(302, err.redirectLocation);
}
else if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_NOT_FOUND) {
return res.status(404).send('Route Not Found!');
}
else {
// for ReactEngine.reactRouterServerErrors.MATCH_INTERNAL_ERROR or
// any other error we just send the error message back
return res.status(500).send(err.message);
}
});
```
### Yeoman Generator
There is a Yeoman generator available to create a new express or KrakenJS application which uses react-engine:
[generator-react-engine](https://www.npmjs.com/package/generator-react-engine).
Expand All @@ -174,7 +212,7 @@ function collector(stats) {
}

var engine = require('react-engine').server.create({
reactRoutes: './routes.jsx'
routes: './routes.jsx'
performanceCollector: collector
});
```
Expand All @@ -185,6 +223,10 @@ var engine = require('react-engine').server.create({
* You can use `js` as the engine if you decide not to write your react views in `jsx`.
* [Blog on react-engine](https://www.paypal-engineering.com/2015/04/27/isomorphic-react-apps-with-react-engine/)
### Migration from 2.x to 3.x
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.
Then, add to your express error middleware, react-engine's MATCH_REDIRECT and MATCH_NOT_FOUND checks.
### Migration from 1.x to 2.x
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.
Expand Down
60 changes: 44 additions & 16 deletions example/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ This movie catalog app illustrates the usage of react-engine to build and run an

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

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

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

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

var routes = module.exports = (
<Router.Route path='/' handler={Layout}>
<Router.DefaultRoute name='list' handler={ListPage} />
<Router.Route name='detail' path='/:id' handler={DetailPage} />
</Router.Route>
<Router>
<Route path='/' component={Layout}>
<IndexRoute component={ListPage} />
<Route path='/movie/:id' component={DetailPage} />
<Redirect from='/gohome' to='/' />
</Route>
</Router>
);
```

Expand All @@ -96,17 +99,19 @@ $ open http://localhost:3000
<head>
<meta charSet='utf-8' />
<title>React Engine Example App</title>
<link rel='stylesheet' href='/styles.css'></link>
</head>
<body>
<div>
{/* Component that renders the active child route handler of a parent route handler component. */}
<Router.RouteHandler {...this.props} />
{/* 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 */}
{this.props.children}
</div>
<script src='/bundle.js'></script>
</body>
</html>
);
}
});
});

// public/views/list.jsx file contains the catalog view elements of our app.
// we iterate through the array of movies and display them on this page.
Expand All @@ -119,13 +124,14 @@ $ open http://localhost:3000
<ul>
{this.props.movies.map(function(movie) {
return (
<li>
<Router.Link to='detail' params={{id: movie.id}}>
<li key={movie.id}>
<Router.Link to={'/movie/' + movie.id}>
<img src={movie.image} alt={movie.title} />
</Router.Link>
</li>
);
})}

</ul>
</div>
);
Expand All @@ -135,12 +141,12 @@ $ open http://localhost:3000
// public/views/detail.jsx file contains the markup to
// display the detail information of a movie
module.exports = React.createClass({
mixins: [Router.State],
render: function render() {
var movieId = this.getParams().id;
var movieId = this.props.params.id;
var movie = this.props.movies.filter(function(_movie) {
return _movie.id === movieId;
})[0];

return (
<div id='detail'>
<h1>{movie.title}</h1>
Expand Down Expand Up @@ -198,6 +204,28 @@ $ open http://localhost:3000
});
});

// add the error handler middleware
app.use(function(err, req, res, next) {
console.error(err);

// http://expressjs.com/en/guide/error-handling.html
if (res.headersSent) {
return next(err);
}

if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_REDIRECT) {
return res.redirect(302, err.redirectLocation);
}
else if (err._type && err._type === ReactEngine.reactRouterServerErrors.MATCH_NOT_FOUND) {
return res.status(404).send('Route Not Found!');
}
else {
// for ReactEngine.reactRouterServerErrors.MATCH_INTERNAL_ERROR or
// any other error we just send the error message back
return res.status(500).send(err.message);
}
});

// the last step in the server side is to configure the express app to listen on port 3000
app.listen(3000, function() {
console.log('Example app listening at http://localhost:%s', PORT);
Expand Down
44 changes: 2 additions & 42 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,7 @@
'use strict';

require('babel-register')({
presets: ['react']
presets: ['es2015', 'react']
});

var PORT = 3000;
var path = require('path');
var movies = require('./movies.json');
var express = require('express');
var renderer = require('react-engine');

var app = express();

// create the view engine with `react-engine`
var reactRoutesFilePath = path.join(__dirname + '/public/routes.jsx');

var engine = renderer.server.create({
routes: require(reactRoutesFilePath),
routesFilePath: reactRoutesFilePath
});

// set the engine
app.engine('.jsx', engine);

// set the view directory
app.set('views', path.join(__dirname, '/public/views'));

// set jsx as the view engine
app.set('view engine', 'jsx');

// finally, set the custom view
app.set('view', renderer.expressView);

// expose public folder as static assets
app.use(express.static(path.join(__dirname, '/public')));

// add the our app routes
app.get('*', function(req, res) {
res.render(req.url, {
movies: movies
});
});

var server = app.listen(PORT, function() {
console.log('Example app listening at http://localhost:%s', PORT);
});
require('./server');
9 changes: 6 additions & 3 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
"dependencies": {
"babel-core": "^6.3.17",
"babel-loader": "^6.2.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-register": "^6.3.13",
"express": "^4.13.3",
"history": "^1.17.0",
"json-loader": "^0.5.4",
"react": "^0.13.3",
"react-engine": "^2.6.0",
"react-router": "^0.13.5",
"react": "^0.14.3",
"react-engine": "3.0.0-rc.2",
"react-router": "^1.0.3",
"serve-favicon": "^2.3.0",
"webpack": "^1.12.9"
}
}
Binary file added example/public/favicon.ico
Binary file not shown.
22 changes: 12 additions & 10 deletions example/public/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@

'use strict';

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

var Layout = require('./views/layout.jsx');
var ListPage = require('./views/list.jsx');
var DetailPage = require('./views/detail.jsx');
import Layout from './views/layout.jsx';
import ListPage from './views/list.jsx';
import DetailPage from './views/detail.jsx';

var routes = module.exports = (
<Router.Route path='/' handler={Layout}>
<Router.DefaultRoute name='list' handler={ListPage} />
<Router.Route name='detail' path='/:id' handler={DetailPage} />
</Router.Route>
<Router>
<Route path='/' component={Layout}>
<IndexRoute component={ListPage} />
<Route path='/movie/:id' component={DetailPage} />
<Redirect from='/gohome' to='/' />
</Route>
</Router>
);
5 changes: 1 addition & 4 deletions example/public/views/detail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
'use strict';

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

module.exports = React.createClass({

mixins: [Router.State],

render: function render() {
var movieId = this.getParams().id;
var movieId = this.props.params.id;
var movie = this.props.movies.filter(function(_movie) {
return _movie.id === movieId;
})[0];
Expand Down
7 changes: 3 additions & 4 deletions example/public/views/layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
'use strict';

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

module.exports = React.createClass({

Expand All @@ -31,11 +30,11 @@ module.exports = React.createClass({
</head>
<body>
<div>
{/* Component that renders the active child route handler of a parent route handler component. */}
<Router.RouteHandler {...this.props} />
{/* 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 */}
{this.props.children}
</div>
<script src='/bundle.js'></script>
</body>
<script src='/bundle.js'></script>
</html>
);
}
Expand Down
4 changes: 2 additions & 2 deletions example/public/views/list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ module.exports = React.createClass({
<ul>
{this.props.movies.map(function(movie) {
return (
<li>
<Router.Link to='detail' params={{id: movie.id}}>
<li key={movie.id}>
<Router.Link to={'/movie/' + movie.id}>
<img src={movie.image} alt={movie.title} />
</Router.Link>
</li>
Expand Down
Loading

0 comments on commit f26a1b6

Please sign in to comment.