Skip to content

Commit 0916566

Browse files
committed
Include redux-router v1.0.0-beta3
1 parent b4a7989 commit 0916566

File tree

13 files changed

+240
-176
lines changed

13 files changed

+240
-176
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![devDependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example/dev-status.svg)](https://david-dm.org/erikras/react-redux-universal-hot-example#info=devDependencies)
88
[![PayPal donate button](http://img.shields.io/paypal/donate.png?color=yellowgreen)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=E2LK57ZQ9YRMN)
99

10-
---
10+
---
1111

1212
## About
1313

@@ -22,8 +22,9 @@ This is a starter boiler plate app I've put together using the following technol
2222
* [Webpack](http://webpack.github.io) for bundling
2323
* [Webpack Dev Middleware](http://webpack.github.io/docs/webpack-dev-middleware.html)
2424
* [Webpack Hot Middleware](https://github.com/glenjamin/webpack-hot-middleware)
25-
* [Redux](https://github.com/gaearon/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation
25+
* [Redux](https://github.com/rackt/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation
2626
* [Redux Dev Tools](https://github.com/gaearon/redux-devtools) for next generation DX (developer experience). Watch [Dan Abramov's talk](https://www.youtube.com/watch?v=xsSnOQynTHs).
27+
* [Redux Router](https://github.com/rackt/redux-router) Keep your router state in your Redux store
2728
* [ESLint](http://eslint.org) to maintain a consistent code style
2829
* [redux-form](https://github.com/erikras/redux-form) to manage form state in Redux
2930
* [lru-memoize](https://github.com/erikras/lru-memoize) to speed up form validation
@@ -60,8 +61,8 @@ A demonstration of this app can be seen [running on heroku](https://react-redux.
6061

6162
## Explanation
6263

63-
What initally gets run is `bin/server.js`, which does little more than enable ES6 and ES7 awesomeness in the
64-
server-side node code. It then initiates `server.js`. In `server.js` we proxy any requests to `/api/*` to the
64+
What initally gets run is `bin/server.js`, which does little more than enable ES6 and ES7 awesomeness in the
65+
server-side node code. It then initiates `server.js`. In `server.js` we proxy any requests to `/api/*` to the
6566
[API server](#api-server), running at `localhost:3030`. All the data fetching calls from the client go to `/api/*`.
6667
Aside from serving the favicon and static content from `/static`, the only thing `server.js` does is initiate delegate
6768
rendering to `react-router`. At the bottom of `server.js`, we listen to port `3000` and initiate the API server.
@@ -93,7 +94,7 @@ The middleware, [`clientMiddleware.js`](https://github.com/erikras/react-redux-u
9394

9495
#### Redux Modules... *What the Duck*?
9596

96-
The `src/redux/modules` folder contains "modules" to help
97+
The `src/redux/modules` folder contains "modules" to help
9798
isolate concerns within a Redux application (aka [Ducks](https://github.com/erikras/ducks-modular-redux), a Redux Style Proposal that I came up with). I encourage you to read the
9899
[Ducks Docs](https://github.com/erikras/ducks-modular-redux) and provide feedback.
99100

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"serialize-javascript": "1.1.2",
108108
"serve-favicon": "2.3.0",
109109
"serve-static": "1.10.0",
110+
"shallowequal": "0.2.2",
110111
"socket.io": "1.3.7",
111112
"socket.io-client": "1.3.7",
112113
"superagent": "1.4.0",
@@ -146,6 +147,7 @@
146147
"react-hot-loader": "1.3.0",
147148
"react-transform-hmr": "1.0.1",
148149
"redux-devtools": "2.1.5",
150+
"redux-router": "1.0.0-beta3",
149151
"sass-loader": "2.0.1",
150152
"strip-loader": "0.1.0",
151153
"style-loader": "0.12.4",

src/client.js

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ import 'babel/polyfill';
55
import React from 'react';
66
import ReactDOM from 'react-dom';
77
import createHistory from 'history/lib/createBrowserHistory';
8-
import createLocation from 'history/lib/createLocation';
98
import createStore from './redux/create';
109
import ApiClient from './helpers/ApiClient';
11-
import universalRouter from './helpers/universalRouter';
1210
import io from 'socket.io-client';
11+
import {Provider} from 'react-redux';
12+
import {reduxReactRouter, ReduxRouter} from 'redux-router';
13+
14+
import routes from './routes';
1315

14-
const history = createHistory();
1516
const client = new ApiClient();
1617

1718
const dest = document.getElementById('content');
18-
const store = createStore(client, window.__data);
19+
const store = createStore(reduxReactRouter, null, createHistory, client, window.__data);
1920

2021
function initSocket() {
2122
const socket = io('', {path: '/api/ws', transports: ['polling']});
@@ -32,34 +33,13 @@ function initSocket() {
3233

3334
global.socket = initSocket();
3435

35-
const location = createLocation(document.location.pathname, document.location.search);
36-
37-
const render = (loc, hist, str, preload) => {
38-
return universalRouter(loc, hist, str, preload)
39-
.then(({component}) => {
40-
ReactDOM.render(component, dest);
41-
if (__DEVTOOLS__) {
42-
const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
43-
ReactDOM.render(<div>
44-
{component}
45-
<DebugPanel top right bottom key="debugPanel">
46-
<DevTools store={store} monitor={LogMonitor}/>
47-
</DebugPanel>
48-
</div>, dest);
49-
}
50-
}, (error) => {
51-
console.error(error);
52-
});
53-
};
54-
55-
history.listen(() => {});
36+
const component = (
37+
<Provider store={store} key="provider">
38+
<ReduxRouter routes={routes} />
39+
</Provider>
40+
);
5641

57-
history.listenBefore((loc, callback) => {
58-
render(loc, history, store, true)
59-
.then((callback));
60-
});
61-
62-
render(location, history, store);
42+
ReactDOM.render(component, dest);
6343

6444
if (process.env.NODE_ENV !== 'production') {
6545
window.React = React; // enable debugger
@@ -69,3 +49,13 @@ if (process.env.NODE_ENV !== 'production') {
6949
console.error('Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.');
7050
}
7151
}
52+
53+
if (__DEVTOOLS__) {
54+
const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
55+
ReactDOM.render(<div>
56+
{component}
57+
<DebugPanel top right bottom key="debugPanel">
58+
<DevTools store={store} monitor={LogMonitor}/>
59+
</DebugPanel>
60+
</div>, dest);
61+
}

src/containers/App/App.js

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import DocumentMeta from 'react-document-meta';
66
import { isLoaded as isInfoLoaded, load as loadInfo } from 'redux/modules/info';
77
import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth';
88
import { InfoBar } from 'components';
9+
import DataFetcher from '../../helpers/DataFetcher';
910

1011
const title = 'React Redux Example';
1112
const description = 'All the modern best practices in one example.';
@@ -34,6 +35,14 @@ const meta = {
3435
}
3536
};
3637

38+
const NavbarLink = ({to, children}) => (
39+
<Link to={to} activeStyle={{
40+
color: 'red'
41+
}}>
42+
{children}
43+
</Link>
44+
);
45+
3746
@connect(
3847
state => ({user: state.auth.user}),
3948
dispatch => bindActionCreators({logout}, dispatch))
@@ -79,46 +88,48 @@ export default class App extends Component {
7988
const {user} = this.props;
8089
const styles = require('./App.scss');
8190
return (
82-
<div className={styles.app}>
83-
<DocumentMeta {...meta}/>
84-
<nav className="navbar navbar-default navbar-fixed-top">
85-
<div className="container">
86-
<Link to="/" className="navbar-brand">
87-
<div className={styles.brand}/>
88-
React Redux Example
89-
</Link>
91+
<DataFetcher>
92+
<div className={styles.app}>
93+
<DocumentMeta {...meta}/>
94+
<nav className="navbar navbar-default navbar-fixed-top">
95+
<div className="container">
96+
<Link to="/" className="navbar-brand">
97+
<div className={styles.brand}/>
98+
React Redux Example
99+
</Link>
90100

91-
<ul className="nav navbar-nav">
92-
{user && <li><Link to="/chat">Chat</Link></li>}
101+
<ul className="nav navbar-nav">
102+
{user && <li><NavbarLink to="/chat">Chat</NavbarLink></li>}
93103

94-
<li><Link to="/widgets">Widgets</Link></li>
95-
<li><Link to="/survey">Survey</Link></li>
96-
<li><Link to="/about">About Us</Link></li>
97-
{!user && <li><Link to="/login">Login</Link></li>}
98-
{user && <li className="logout-link"><a href="/logout" onClick={::this.handleLogout}>Logout</a></li>}
99-
</ul>
100-
{user &&
101-
<p className={styles.loggedInMessage + ' navbar-text'}>Logged in as <strong>{user.name}</strong>.</p>}
102-
<ul className="nav navbar-nav navbar-right">
103-
<li>
104-
<a href="https://github.com/erikras/react-redux-universal-hot-example"
105-
target="_blank" title="View on Github"><i className="fa fa-github"/></a>
106-
</li>
107-
</ul>
104+
<li><NavbarLink to="/widgets">Widgets</NavbarLink></li>
105+
<li><NavbarLink to="/survey">Survey</NavbarLink></li>
106+
<li><NavbarLink to="/about">About Us</NavbarLink></li>
107+
{!user && <li><NavbarLink to="/login">Login</NavbarLink></li>}
108+
{user && <li className="logout-link"><a href="/logout" onClick={::this.handleLogout}>Logout</a></li>}
109+
</ul>
110+
{user &&
111+
<p className={styles.loggedInMessage + ' navbar-text'}>Logged in as <strong>{user.name}</strong>.</p>}
112+
<ul className="nav navbar-nav navbar-right">
113+
<li>
114+
<a href="https://github.com/erikras/react-redux-universal-hot-example"
115+
target="_blank" title="View on Github"><i className="fa fa-github"/></a>
116+
</li>
117+
</ul>
118+
</div>
119+
</nav>
120+
<div className={styles.appContent}>
121+
{this.props.children}
108122
</div>
109-
</nav>
110-
<div className={styles.appContent}>
111-
{this.props.children}
112-
</div>
113-
<InfoBar/>
123+
<InfoBar/>
114124

115-
<div className="well text-center">
116-
Have questions? Ask for help <a
117-
href="https://github.com/erikras/react-redux-universal-hot-example/issues"
118-
target="_blank">on Github</a> or in the <a
119-
href="http://www.reactiflux.com/" target="_blank">#react-redux-universal</a> Slack channel.
125+
<div className="well text-center">
126+
Have questions? Ask for help <a
127+
href="https://github.com/erikras/react-redux-universal-hot-example/issues"
128+
target="_blank">on Github</a> or in the <a
129+
href="http://www.reactiflux.com/" target="_blank">#react-redux-universal</a> Slack channel.
130+
</div>
120131
</div>
121-
</div>
132+
</DataFetcher>
122133
);
123134
}
124135
}

src/containers/Home/Home.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,16 @@ export default class Home extends Component {
6969
<li><a href="http://webpack.github.io/docs/webpack-dev-middleware.html" target="_blank">Webpack Dev Middleware</a>
7070
</li>
7171
<li><a href="https://github.com/glenjamin/webpack-hot-middleware" target="_blank">Webpack Hot Middleware</a></li>
72-
<li><a href="https://github.com/gaearon/redux" target="_blank">Redux</a>'s futuristic <a
72+
<li><a href="https://github.com/rackt/redux" target="_blank">Redux</a>'s futuristic <a
7373
href="https://facebook.github.io/react/blog/2014/05/06/flux.html" target="_blank">Flux</a> implementation
7474
</li>
7575
<li><a href="https://github.com/gaearon/redux-devtools" target="_blank">Redux Dev Tools</a> for next
7676
generation DX (developer experience).
7777
Watch <a href="https://www.youtube.com/watch?v=xsSnOQynTHs" target="_blank">Dan Abramov's talk</a>.
7878
</li>
79+
<li><a href="https://github.com/rackt/redux-router" target="_blank">Redux Router</a> Keep
80+
your router state in your Redux store
81+
</li>
7982
<li><a href="http://eslint.org" target="_blank">ESLint</a> to maintain a consistent code style</li>
8083
<li><a href="https://github.com/erikras/redux-form" target="_blank">redux-form</a> to manage form state
8184
in Redux
@@ -103,9 +106,8 @@ export default class Home extends Component {
103106
<dd>
104107
The <Link to="/widgets">Widgets page</Link> demonstrates how to fetch data asynchronously from
105108
some source that is needed to complete the server-side rendering. <code>Widgets.js</code>'s
106-
<code>fetchData()</code> function is called from <code>universalRouter.js</code> before
107-
the widgets page is loaded, on either the server or the client, allowing all the widget data
108-
to be loaded and ready for the page to render.
109+
<code>fetchData()</code> function is called before the widgets page is loaded, on either the server
110+
or the client, allowing all the widget data to be loaded and ready for the page to render.
109111
</dd>
110112
<dt>Data loading errors</dt>
111113
<dd>

src/helpers/DataFetcher.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Component, PropTypes } from 'react';
2+
import {connect} from 'react-redux';
3+
4+
import shallowequal from 'shallowequal';
5+
6+
import getDataDependencies from './getDataDependencies';
7+
8+
@connect((state) => {return {storeState: state};})
9+
export default class DataFetcher extends Component {
10+
static propTypes = {
11+
children: PropTypes.object.isRequired,
12+
storeState: PropTypes.object.isRequired
13+
}
14+
15+
static contextTypes = {
16+
store: PropTypes.object
17+
}
18+
19+
constructor(props, context) {
20+
super(props, context);
21+
22+
this.state = {
23+
isFetching: false
24+
};
25+
}
26+
27+
componentDidMount() {
28+
this._isMounted = true;
29+
}
30+
31+
componentWillReceiveProps(nextProps) {
32+
const {router} = nextProps.storeState;
33+
34+
if (shallowequal(router.location, this.props.storeState.router.location)) {
35+
return;
36+
}
37+
38+
const promises = getDataDependencies(this.context.store);
39+
40+
if (promises.length > 0) {
41+
this.setState({
42+
isFetching: true
43+
});
44+
45+
Promise.all(promises).then(this.willTransition.bind(this, nextProps));
46+
} else if (this.state.isFetching) {
47+
this.setState({
48+
isFetching: false
49+
});
50+
}
51+
}
52+
53+
shouldComponentUpdate(nextProps, nextState) {
54+
return !nextState.isFetching;
55+
}
56+
57+
componentWillUnmount() {
58+
this._isMounted = false;
59+
}
60+
61+
// For cases where the user presses another link before we're done,
62+
// we need to check that our location data is the most recent.
63+
// This could be avoided by using promises that support cancellation.
64+
willTransition(transitionProps) {
65+
if (!this._isMounted) {
66+
return;
67+
}
68+
69+
if (!shallowequal(this.props.storeState.router.location, transitionProps.storeState.router.location)) {
70+
return;
71+
}
72+
73+
this.setState({
74+
isFetching: false
75+
});
76+
}
77+
78+
render() {
79+
return this.props.children;
80+
}
81+
}

src/helpers/getDataDependencies.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const getDataDependency = (component = {}) => {
2+
return component.WrappedComponent ?
3+
getDataDependency(component.WrappedComponent) :
4+
component.fetchData;
5+
};
6+
7+
export default (store) => {
8+
const {router} = store.getState();
9+
10+
return router.components
11+
.filter((component) => getDataDependency(component)) // only look at ones with a static fetchData()
12+
.map(getDataDependency) // pull out fetch data methods
13+
.map(fetchData =>
14+
fetchData(store)); // call fetch data methods and save promises
15+
};

src/helpers/getStatusFromRoutes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default (routes) => {
2+
return routes.reduce((prev, cur) => {
3+
return cur.status || prev.status;
4+
});
5+
};

0 commit comments

Comments
 (0)