Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e0e44f4
hoist-non-react-statics no longer needed
Jan 14, 2016
b5b9370
Fix link to redux-devtools
c089 Jan 14, 2016
5557263
Update react-bootstrap imports
markusjwetzel Jan 18, 2016
697da08
Update README.md
quicksnap Jan 22, 2016
0277309
docs(guide): Add 'Adding Text to Home Page' guide
merriam Jan 24, 2016
da02762
Merge pull request #862 from merriam/master
quicksnap Jan 24, 2016
bc45a02
Merge pull request #840 from markusjwetzel/master
quicksnap Jan 24, 2016
de8eb1c
remote duplicate conf
Jan 25, 2016
4f5ee87
Merge pull request #867 from yaoqiang/master
quicksnap Jan 26, 2016
4d4a462
Merge pull request #825 from c089/patch-3
quicksnap Jan 26, 2016
b0788fe
Merge pull request #824 from svrcekmichal/master
quicksnap Jan 26, 2016
57e4a38
chore(multireducer) upgraded multireducer to v2.0.0
erikras Jan 26, 2016
888227c
style(CounterButton) cleaned up decorator code
erikras Jan 26, 2016
f3cab9b
docs(guide): Add 'Adding A Page' guide
merriam Jan 30, 2016
e4715f7
feat(ci): add support for circle-ci
Feb 8, 2016
6e3dcf7
Migration to redux simple router and async-props
Jan 15, 2016
919a24a
Migration to redux-async-props
Jan 20, 2016
2f767e5
test infoBar fix
Jan 20, 2016
bae54ed
redux-async-connect 0.1.4
Jan 20, 2016
b963ac2
Fix duplicate re-render.
quicksnap Jan 24, 2016
f5d42fe
redux-async connect updated to 0.1.10
Jan 25, 2016
e327fe4
react-router-redux updated to v3.0.0
Feb 11, 2016
a7a55b7
Removing dependency from history module
Feb 11, 2016
97773e5
Scroll behavior
Feb 13, 2016
a4a2c05
removing unused getRoutes from createStore function
Feb 14, 2016
5215f66
Merge pull request #833 from sars/master
quicksnap Feb 15, 2016
c836198
Update README.md
quicksnap Feb 15, 2016
318027e
Merge pull request #910 from zaccolley/circle-ci-#908
erikras Feb 15, 2016
3fdcf22
Merge pull request #883 from merriam/master
erikras Feb 15, 2016
b1f8dd1
Revert fetchDataDeferred behaviour on Widgets page
Jan 26, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ This is a starter boilerplate app I've put together using the following technolo
* [Webpack Dev Middleware](http://webpack.github.io/docs/webpack-dev-middleware.html)
* [Webpack Hot Middleware](https://github.com/glenjamin/webpack-hot-middleware)
* [Redux](https://github.com/rackt/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation
* [Redux Dev Tools](https://github.com/rackt/redux-devtools) for next generation DX (developer experience). Watch [Dan Abramov's talk](https://www.youtube.com/watch?v=xsSnOQynTHs).
* [Redux Router](https://github.com/acdlite/redux-router) Keep your router state in your Redux store
* [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).
* [React Router Redux](https://github.com/reactjs/react-router-redux) Redux/React Router bindings.
* [ESLint](http://eslint.org) to maintain a consistent code style
* [redux-form](https://github.com/erikras/redux-form) to manage form state in Redux
* [lru-memoize](https://github.com/erikras/lru-memoize) to speed up form validation
Expand Down Expand Up @@ -80,6 +80,8 @@ A demonstration of this app can be seen [running on heroku](https://react-redux.

* [Exploring the Demo App](docs/ExploringTheDemoApp/ExploringTheDemoApp.md) is a guide that can be used before you install the kit.
* [Installing the Kit](docs/InstallingTheKit/InstallingTheKit.md) guides you through installation and running the development server locally.
* [Adding Text to the Home Page](docs/AddingToHomePage/AddingToHomePage.md) guides you through adding "Hello, World!" to the home page.
* [Adding A Page](docs/AddingAPage/AddingAPage.md) guides you through adding a new page.
* [React Tutorial - Converting Reflux to Redux](http://engineering.wework.com/process/2015/10/01/react-reflux-to-redux/), by Matt Star
If you are the kind of person that learns best by following along a tutorial, I can recommend Matt Star's overview and examples.

Expand Down
17 changes: 17 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
machine:
node:
version: 4.0
environment:
CONTINUOUS_INTEGRATION: true

dependencies:
cache_directories:
- node_modules
override:
- npm prune && npm install

test:
override:
- npm run lint
- npm test
- npm run test-node
93 changes: 93 additions & 0 deletions docs/AddingAPage/AddingAPage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Adding A Hello Page

This guide adds a `/hello` page to the sample application by
following the existing outline.

## Using Ack on About

Searching strings is one way to [grok](https://en.wikipedia.org/wiki/Grok) the structure
of the kit and sample application. You can use *grep* or [ack](http://beyondgrep.com) (`brew install ack`).
I use *ack* with this alias:

![ick Alias](ick_alias.png)

Looking with `ick about` and ignoring documentation, the word *about* appears in these files:

![ick for About](ick_about.png)

## Add the Hello page container

A new page requires new page renderer. Copy the About page to a
new directory and trim out almost all of it:

* `cd ./src/containers && mkdir ./src/Hello` because each container goes in its own
directory by convention.
* `cp About/About.js Hello/Hello.js`

Edit `Hello/Hello.js` into this file:

![New Hello.js](new_hello.png)



## Edit three files to add Hello

#### Add to `./src/containers/index.js` to include and export the React component:

![Edit index.js](edit_index.png)

#### Add to `./routes.js` to connect the `/hello` url path to the component:

![Edit routes.js 1](edit_route1.png)
![Edit routes.js 2](edit_route2.png)

#### Add to `./src/containers/App/App.js` to add "Hello" to the NavBar

![Edit App.js](edit_app.png)

And voila, the new 'Hello' page:

![Show Hello](show_hello.png)

# Take-away: Notice the trade-offs

The task of adding a new page exemplifies two trade-offs in the kit:
**code versus convention** and the **cut and paste** style.

Convention is a set of constraining rules that automatically trigger
routine configuration tasks. For example, WebPack automatically picked up the
new directory `./src/containers/Hello` without adding to any configuration files.

On the other hand, routine code was added to `./src/containers/index.js` and
`./src/routes.js` to handle the new page. A convention could automatically
accomplish the same tasks at either compile or run time. The cost is new
constraints, such as requiring `Hello/Hello.js` to be renamed
`HelloPage/HelloPage.js`.

Following a style in the code that has no automatic effects is just organic
growth, not convention. For example, developers reading `./src/containers/index.js`
must stop and figure out why all subdirectories except `DevTools` are exported.
(`DevTools`)[`./src/containers/DevTools/DevTools.js`](https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/containers/DevTools/DevTools.js)
contains a single function which should be
[randomly](https://github.com/erikras/react-redux-universal-hot-example/issues/808)
moved to `./src/utils` or `./src/helpers`. Using a convention rule that all
containers must contain an exported React component would raise an error.
Organic growth leads to disorder in a project.

Similarly, the **cut and paste** style of coding also degrades the project.
For example, In `App.js`, the new *NavItem* tag included a new value for the
*eventkey* property. The *eventkey* property is
[poorly](https://github.com/react-bootstrap/react-bootstrap/issues/320)
[understood](https://github.com/react-bootstrap/react-bootstrap/issues/432).
All *eventkey* fields in `App.js` are unused and can be removed. The
**cut and paste** style just compounds an
[old error](https://github.com/erikras/react-redux-universal-hot-example/commit/d67a79c1e7da5367dc8922019ca726e69d56bf0e)
and reinforces confusion.

![Edit App revisted](edit_app2.png)

The use of the **cut and paste** style raises well known issues in
maintenance, documentation, and code quality. It is not for use in
production code.

Some choices about trade-offs are easier than others.
Binary file added docs/AddingAPage/edit_app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/edit_app2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/edit_index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/edit_route1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/edit_route2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/ick_about.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/ick_alias.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/new_hello.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingAPage/show_hello.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions docs/AddingToHomePage/AddingToHomePage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Adding Hello, World as static text

Printing *Hello, World!* is a traditional task. This guides you through adding the text "Hello, World!" to the
home page of the sample application.

## Find the home page

First, find the correct file to change by walking through the kit's directory tree:

![Finding The Home Page 1](find_home1.png)


![Finding The Home Page 2](find_home2.png)

![Finding The Home Page 3](find_home3.png)

![Finding The Home Page 4](find_home4.png)

So, the likely file is `src/containers/Home/Home.js`.

## Start the server and open the browser

Execute `npm run dev` and open http://localhost:3000:

* `./package.json`, using [concurrently](https://www.npmjs.com/package/concurrently)
and [better-npm-run](https://www.npmjs.com/package/better-npm-run), runs
`./webpack/webpack-dev-server.js` on port 3001; runs `./bin/server.js` for HTTP on port 3000;
and runs `./bin/api.js` for the REST API on port 3030.

* `./bin/server.js` calls `./src/server.js` and uses the [HMR plugin](http://andrewhfarmer.com/webpack-hmr-tutorial/)
for hot reloading, meaning the browser refreshes automatically when any file in `./src` is changed.

* `./webpack/webpack-dev-server` does teh actual compilation with the
[webpack dev middleware package](https://github.com/webpack/webpack-dev-middleware) to provide a key feature found
in Glup: compilation without writing intermediate files to disk. Configuring webpack
[can be confusing](https://medium.com/@dtothefp/why-can-t-anyone-write-a-simple-webpack-tutorial-d0b075db35ed#.cle1vv5ql).

* `./bin/api.js` calls `./api/api.js`. It receives incoming REST requests as JSON objects and responds with
other JSON objects.

## Change the text

Add the static text to (`src/containers/Home/Home.js`):

![Add Hello Header to Home](add_home.png)


When you save the file to disk, the change to the `./src` directory is picked up by the
[piping](https://www.npmjs.com/package/piping) module, triggering the webpack-dev-server to rebuild
`./static/dist/[checksum].js`, and triggering a stub injected into the HTML file served to the browser to
reload. The rebuilding processes through webpack middleware and plugins that compile `*.sccs` files,
transpile JAX and ES6 (or ES7), and bundles together all the resources into one package in about 6 seconds.
That is, the browser will show "Hello, World!" on your web page in about 6 seconds:

![Hello World rendered on home page](hello_rendered.png)

## Conclusion

You added **Hello, World!**. The process is [as clear as is the summer's sun](https://www.youtube.com/watch?v=EhGiSfv5FJk&t=3m23s).

Binary file added docs/AddingToHomePage/add_home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingToHomePage/find_home1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingToHomePage/find_home2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingToHomePage/find_home3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingToHomePage/find_home4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/AddingToHomePage/hello_rendered.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = function (config) {

browsers: ['PhantomJS'],

singleRun: !!process.env.CONTINUOUS_INTEGRATION,
singleRun: !!process.env.CI,

frameworks: [ 'mocha' ],

Expand Down
14 changes: 6 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,31 +88,29 @@
"compression": "^1.6.0",
"express": "^4.13.3",
"express-session": "^1.12.1",
"history": "1.17.0",
"file-loader": "^0.8.5",
"hoist-non-react-statics": "^1.0.3",
"http-proxy": "^1.12.0",
"invariant": "^2.2.0",
"less": "^2.5.3",
"less-loader": "^2.2.1",
"lru-memoize": "^1.0.0",
"map-props": "^1.0.0",
"multireducer": "^1.0.2",
"multireducer": "^2.0.0",
"piping": "^0.3.0",
"pretty-error": "^1.2.0",
"query-string": "^3.0.0",
"react": "^0.14.2",
"react-bootstrap": "^0.28.1",
"react-dom": "^0.14.1",
"react-helmet": "^2.2.0",
"react-inline-css": "^2.0.0",
"react-redux": "^4.0.0",
"react-router": "1.0.3",
"react-router-bootstrap": "^0.19.3",
"react-router": "2.0.0",
"react-router-bootstrap": "^0.20.1",
"react-router-redux": "^3.0.0",
"redux": "^3.0.4",
"redux-async-connect": "^0.1.13",
"redux-form": "^3.0.12",
"redux-router": "1.0.0-beta5",
"scroll-behavior": "^0.3.0",
"scroll-behavior": "^0.3.2",
"serialize-javascript": "^1.1.2",
"serve-favicon": "^2.3.0",
"socket.io": "^1.3.7",
Expand Down
21 changes: 10 additions & 11 deletions src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@
import 'babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import createHistory from 'history/lib/createBrowserHistory';
import useScroll from 'scroll-behavior/lib/useStandardScroll';
import createStore from './redux/create';
import ApiClient from './helpers/ApiClient';
import io from 'socket.io-client';
import {Provider} from 'react-redux';
import {reduxReactRouter, ReduxRouter} from 'redux-router';
import { Router, browserHistory } from 'react-router';
import { ReduxAsyncConnect } from 'redux-async-connect';
import useScroll from 'scroll-behavior/lib/useStandardScroll';

import getRoutes from './routes';
import makeRouteHooksSafe from './helpers/makeRouteHooksSafe';

const client = new ApiClient();

// Three different types of scroll behavior available.
// Documented here: https://github.com/rackt/scroll-behavior
const scrollableHistory = useScroll(createHistory);

const history = useScroll(() => browserHistory)();
const dest = document.getElementById('content');
const store = createStore(reduxReactRouter, makeRouteHooksSafe(getRoutes), scrollableHistory, client, window.__data);
const store = createStore(history, client, window.__data);

function initSocket() {
const socket = io('', {path: '/ws'});
Expand All @@ -40,7 +35,11 @@ function initSocket() {
global.socket = initSocket();

const component = (
<ReduxRouter routes={getRoutes(store)} />
<Router render={(props) =>
<ReduxAsyncConnect {...props} helpers={{client}} />
} history={history}>
{getRoutes(store)}
</Router>
);

ReactDOM.render(
Expand Down
5 changes: 3 additions & 2 deletions src/components/CounterButton/CounterButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import {connectMultireducer} from 'multireducer';
import {increment} from 'redux/modules/counter';

@connectMultireducer(
state => ({count: state.count}),
{increment})
(key, state) => ({count: state.multireducer[key].count}),
{increment}
)
export default class CounterButton extends Component {
static propTypes = {
count: PropTypes.number,
Expand Down
6 changes: 2 additions & 4 deletions src/components/__tests__/InfoBar-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {renderIntoDocument} from 'react-addons-test-utils';
import { expect} from 'chai';
import { InfoBar } from 'components';
import { Provider } from 'react-redux';
import {reduxReactRouter} from 'redux-router';
import createHistory from 'history/lib/createMemoryHistory';
import { browserHistory } from 'react-router';
import createStore from 'redux/create';
import ApiClient from 'helpers/ApiClient';
const client = new ApiClient();
Expand All @@ -22,8 +21,7 @@ describe('InfoBar', () => {
}
}
};

const store = createStore(reduxReactRouter, null, createHistory, client, mockStore);
const store = createStore(browserHistory, client, mockStore);
const renderer = renderIntoDocument(
<Provider store={store} key="provider">
<InfoBar/>
Expand Down
3 changes: 0 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ module.exports = Object.assign({
{property: 'og:card', content: 'summary'},
{property: 'og:site', content: '@erikras'},
{property: 'og:creator', content: '@erikras'},
{property: 'og:title', content: 'React Redux Example'},
{property: 'og:description', content: 'All the modern best practices in one example.'},
{property: 'og:image', content: 'https://react-redux.herokuapp.com/logo.jpg'},
{property: 'og:image:width', content: '200'},
{property: 'og:image:height', content: '200'}
]
Expand Down
41 changes: 22 additions & 19 deletions src/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { IndexLink } from 'react-router';
import { LinkContainer } from 'react-router-bootstrap';
import { Navbar, Nav, NavItem } from 'react-bootstrap';
import Navbar from 'react-bootstrap/lib/Navbar';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import Helmet from 'react-helmet';
import { isLoaded as isInfoLoaded, load as loadInfo } from 'redux/modules/info';
import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth';
import { InfoBar } from 'components';
import { pushState } from 'redux-router';
import connectData from 'helpers/connectData';
import { routeActions } from 'react-router-redux';
import config from '../../config';

function fetchData(getState, dispatch) {
const promises = [];
if (!isInfoLoaded(getState())) {
promises.push(dispatch(loadInfo()));
}
if (!isAuthLoaded(getState())) {
promises.push(dispatch(loadAuth()));
}
return Promise.all(promises);
}

@connectData(fetchData)
@connect(
state => ({user: state.auth.user}),
{logout, pushState})
{logout, pushState: routeActions.push})
export default class App extends Component {
static propTypes = {
children: PropTypes.object.isRequired,
Expand All @@ -41,17 +30,31 @@ export default class App extends Component {
componentWillReceiveProps(nextProps) {
if (!this.props.user && nextProps.user) {
// login
this.props.pushState(null, '/loginSuccess');
this.props.pushState('/loginSuccess');
} else if (this.props.user && !nextProps.user) {
// logout
this.props.pushState(null, '/');
this.props.pushState('/');
}
}

static reduxAsyncConnect(params, store) {
const {dispatch, getState} = store;
const promises = [];

if (!isInfoLoaded(getState())) {
promises.push(dispatch(loadInfo()));
}
if (!isAuthLoaded(getState())) {
promises.push(dispatch(loadAuth()));
}

return Promise.all(promises);
}

handleLogout = (event) => {
event.preventDefault();
this.props.logout();
}
};

render() {
const {user} = this.props;
Expand Down
Loading