diff --git a/README.md b/README.md
deleted file mode 100644
index 41e88a5..0000000
--- a/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# react_on_rails Generator Results
-
-You can find examples of running the generators from [react_on_rails](https://github.com/shakacode/react_on_rails/).
-
-For each release, we'll be publishing a set of pull requests that show the various permutations of running the generator on top of a brand new Rails 4 App (`rails new my_app`).
-
-Run `rails generate react_on_rails:install --help` for descriptions of all available options:
-
-```
-Usage:
- rails generate react_on_rails:install [options]
-
-Options:
- -R, [--redux], [--no-redux] # Install Redux gems and Redux version of Hello World Example
- -S, [--server-rendering], [--no-server-rendering] # Add necessary files and configurations for server-side rendering
- -j, [--skip-js-linters], [--no-skip-js-linters] # Skip installing JavaScript linting files
- -L, [--ruby-linters], [--no-ruby-linters] # Install ruby linting files, tasks, and configs
- -H, [--heroku-deployment], [--no-heroku-deployment] # Install files necessary for deploying to Heroku
-
-Runtime options:
- -f, [--force] # Overwrite files that already exist
- -p, [--pretend], [--no-pretend] # Run but do not make any changes
- -q, [--quiet], [--no-quiet] # Suppress status output
- -s, [--skip], [--no-skip] # Skip files that already exist
-
-Description:
- Create react on rails files for install generator.
-```
-
-See the [react_on_rails README.md](https://github.com/shakacode/react_on_rails/blob/master/README.md) for more information.
-
-Note: We support [react-router](https://github.com/rackt/react-router/), even with server rendering, but we have not yet created generator options for that.
-
-# v1.1.1
-
-* [basic](https://github.com/shakacode/react_on_rails-generator-results/pull/121)
-* [basic-server-rendering](https://github.com/shakacode/react_on_rails-generator-results/pull/122)
-* [redux](https://github.com/shakacode/react_on_rails-generator-results/pull/123)
-* [redux-server-rendering](https://github.com/shakacode/react_on_rails-generator-results/pull/124)
-* [basic-heroku-deployment](https://github.com/shakacode/react_on_rails-generator-results/pull/125)
-* [basic-server-rendering](https://github.com/shakacode/react_on_rails-generator-results/pull/126)
-* [redux to redux-server-rendering](https://github.com/shakacode/react_on_rails-generator-results/pull/127)
-* [basic to redux](https://github.com/shakacode/react_on_rails-generator-results/pull/129)
-* [basic-server-rendering to redux-server-rendering](https://github.com/shakacode/react_on_rails-generator-results/pull/128)
-* [basic to basic-heroku-deployment](https://github.com/shakacode/react_on_rails-generator-results/pull/130)
diff --git a/bin/rails b/bin/rails
index 62687aa..0138d79 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
-rescue LoadError
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
diff --git a/bin/rake b/bin/rake
index 2e9ece4..d87d5f5 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,7 +1,8 @@
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
-rescue LoadError
+rescue LoadError => e
+ raise unless e.message.include?('spring')
end
require_relative '../config/boot'
require 'rake'
diff --git a/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx b/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx
new file mode 100644
index 0000000..f2b884b
--- /dev/null
+++ b/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx
@@ -0,0 +1,8 @@
+import * as actionTypes from '../constants/helloWorldConstants';
+
+export function updateName(name) {
+ return {
+ type: actionTypes.HELLO_WORLD_NAME_UPDATE,
+ name,
+ };
+}
diff --git a/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx b/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx
index b6916f8..645fe09 100644
--- a/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx
+++ b/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx
@@ -1,4 +1,8 @@
+// HelloWorldWidget is an arbitrary name for any "dumb" component. We do not recommend suffixing all your
+// dump component names with Widget.
+
import React, { PropTypes } from 'react';
+import Immutable from 'immutable';
import _ from 'lodash';
// Simple example of a React "dumb" component
@@ -13,25 +17,30 @@ export default class HelloWorldWidget extends React.Component {
}
static propTypes = {
- name: PropTypes.string.isRequired,
- _updateName: PropTypes.func.isRequired,
- };
+ // We prefix all property and variable names pointing to Immutable.js objects with '$$'.
+ // This allows us to immediately know we don't call $$helloWorldStore['someProperty'], but instead use
+ // the Immutable.js `get` API for Immutable.Map
+ actions: PropTypes.object.isRequired,
+ $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired,
+ }
// React will automatically provide us with the event `e`
_handleChange(e) {
const name = e.target.value;
- this.props._updateName(name);
+ this.props.actions.updateName(name);
}
render() {
+ const $$helloWorldStore = this.props.$$helloWorldStore;
+ const name = $$helloWorldStore.get('name');
return (
);
diff --git a/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx b/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx
new file mode 100644
index 0000000..2b5a011
--- /dev/null
+++ b/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx
@@ -0,0 +1,8 @@
+// See https://www.npmjs.com/package/mirror-creator
+// Allows us to easily setup constants inside of
+// client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx
+import mirrorCreator from 'mirror-creator';
+
+export default mirrorCreator([
+ 'HELLO_WORLD_NAME_UPDATE',
+]);
diff --git a/client/app/bundles/HelloWorld/containers/HelloWorld.jsx b/client/app/bundles/HelloWorld/containers/HelloWorld.jsx
index a09a037..82e8c8d 100644
--- a/client/app/bundles/HelloWorld/containers/HelloWorld.jsx
+++ b/client/app/bundles/HelloWorld/containers/HelloWorld.jsx
@@ -1,33 +1,43 @@
import React, { PropTypes } from 'react';
import HelloWorldWidget from '../components/HelloWorldWidget';
-import _ from 'lodash';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import Immutable from 'immutable';
+import * as helloWorldActionCreators from '../actions/helloWorldActionCreators';
+
+function select(state) {
+ // Which part of the Redux global state does our component want to receive as props?
+ // Note the use of `$$` to prefix the property name because the value is of type Immutable.js
+ return { $$helloWorldStore: state.$$helloWorldStore };
+}
// Simple example of a React "smart" component
-export default class HelloWorld extends React.Component {
+class HelloWorld extends React.Component {
constructor(props, context) {
super(props, context);
-
- // Uses lodash to bind all methods to the context of the object instance, otherwise
- // the methods defined here would not refer to the component's class, not the component
- // instance itself.
- _.bindAll(this, '_updateName');
}
static propTypes = {
- name: PropTypes.string.isRequired, // this is passed from the Rails view
- }
+ dispatch: PropTypes.func.isRequired,
- state = {name: this.props.name} // how to set initial state in es2015 class syntax
-
- _updateName(name) {
- this.setState({name: name});
+ // This corresponds to the value used in function select above.
+ $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired,
}
render() {
+ const { dispatch, $$helloWorldStore } = this.props;
+ const actions = bindActionCreators(helloWorldActionCreators, dispatch);
+
+ // This uses the ES2015 spread operator to pass properties as it is more DRY
+ // This is equivalent to:
+ //
return (
-
-
-
+
);
}
}
+
+// Don't forget to actually use connect!
+// Note that we don't export HelloWorld, but the redux "connected" version of it.
+// See https://github.com/rackt/react-redux/blob/master/docs/api.md#examples
+export default connect(select)(HelloWorld);
diff --git a/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx b/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx
new file mode 100644
index 0000000..df77e65
--- /dev/null
+++ b/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx
@@ -0,0 +1,21 @@
+import Immutable from 'immutable';
+
+import * as actionTypes from '../constants/helloWorldConstants';
+
+export const $$initialState = Immutable.fromJS({
+ name: '', // this is the default state that would be used if one were not passed into the store
+});
+
+export default function helloWorldReducer($$state = $$initialState, action) {
+ const { type, name } = action;
+
+ switch (type) {
+ case actionTypes.HELLO_WORLD_NAME_UPDATE: {
+ return $$state.set('name', name);
+ }
+
+ default: {
+ return $$state;
+ }
+ }
+}
diff --git a/client/app/bundles/HelloWorld/reducers/index.jsx b/client/app/bundles/HelloWorld/reducers/index.jsx
new file mode 100644
index 0000000..1556aef
--- /dev/null
+++ b/client/app/bundles/HelloWorld/reducers/index.jsx
@@ -0,0 +1,14 @@
+// This file is our manifest of all reducers for the app.
+// See also /client/app/bundles/HelloWorld/store/helloWorldStore.jsx
+// A real world app will likely have many reducers and it helps to organize them in one file.
+// `https://github.com/shakacode/react_on_rails/tree/master/docs/additional_reading/generated_client_code.md`
+import helloWorldReducer from './helloWorldReducer';
+import { $$initialState as $$helloWorldState } from './helloWorldReducer';
+
+export default {
+ $$helloWorldStore: helloWorldReducer,
+};
+
+export const initalStates = {
+ $$helloWorldState,
+};
diff --git a/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx b/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx
index 46e76ec..e5e9f92 100644
--- a/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx
+++ b/client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx
@@ -1,9 +1,18 @@
import React from 'react';
+import { Provider } from 'react-redux';
+
+import createStore from '../store/helloWorldStore';
import HelloWorld from '../containers/HelloWorld';
+// See documentation for https://github.com/rackt/react-redux.
+// This is how you get props from the Rails view into the redux store.
+// This code here binds your smart component to the redux store.
const HelloWorldApp = props => {
+ const store = createStore(props);
const reactComponent = (
-
+
+
+
);
return reactComponent;
};
diff --git a/client/app/bundles/HelloWorld/store/helloWorldStore.jsx b/client/app/bundles/HelloWorld/store/helloWorldStore.jsx
new file mode 100644
index 0000000..629969b
--- /dev/null
+++ b/client/app/bundles/HelloWorld/store/helloWorldStore.jsx
@@ -0,0 +1,35 @@
+import { compose, createStore, applyMiddleware, combineReducers } from 'redux';
+
+// See https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html
+// This is not actually used for this simple example, but you'd probably want to use this once your app has
+// asynchronous actions.
+import thunkMiddleware from 'redux-thunk';
+
+// This provides an example of logging redux actions to the console.
+// You'd want to disable this for production.
+import loggerMiddleware from 'lib/middlewares/loggerMiddleware';
+
+import reducers from '../reducers';
+import { initalStates } from '../reducers';
+
+export default props => {
+ // This is how we get initial props Rails into redux.
+ const { name } = props;
+ const { $$helloWorldState } = initalStates;
+
+ // Redux expects to initialize the store using an Object, not an Immutable.Map
+ const initialState = {
+ $$helloWorldStore: $$helloWorldState.merge({
+ name: name,
+ }),
+ };
+
+ const reducer = combineReducers(reducers);
+ const composedStore = compose(
+ applyMiddleware(thunkMiddleware, loggerMiddleware)
+ );
+ const storeCreator = composedStore(createStore);
+ const store = storeCreator(reducer, initialState);
+
+ return store;
+};
diff --git a/client/app/lib/middlewares/loggerMiddleware.js b/client/app/lib/middlewares/loggerMiddleware.js
new file mode 100644
index 0000000..1253fe1
--- /dev/null
+++ b/client/app/lib/middlewares/loggerMiddleware.js
@@ -0,0 +1,20 @@
+/* eslint no-console: 0 */
+
+// This logger should be configured not to run in a production environment.
+// See https://github.com/petehunt/webpack-howto#6-feature-flags for you might turn this off for production.
+export default function logger({ getState }) {
+ return next => action => {
+ console.log('will dispatch', action);
+
+ // Call the next dispatch method in the middleware chain.
+ const result = next(action);
+
+ const immutableState = getState();
+
+ console.log('state after dispatch', JSON.stringify(immutableState));
+
+ // This will likely be the action itself, unless
+ // a middleware further in chain changed it.
+ return result;
+ };
+}
diff --git a/client/package.json b/client/package.json
index 00373ab..38f1eff 100644
--- a/client/package.json
+++ b/client/package.json
@@ -41,13 +41,20 @@
"es5-shim": "^4.3.1",
"es6-promise": "^3.0.2",
"expose-loader": "^0.7.1",
+ "immutable": "^3.7.5",
"imports-loader": "^0.6.5",
"jquery": "^2.1.4",
"jquery-ujs": "^1.1.0-1",
"loader-utils": "^0.2.11",
+ "lodash": "^3.10.1",
+ "mirror-creator": "0.0.1",
"react": "^0.14.0",
"react-bootstrap": "^0.28.1",
"react-dom": "^0.14.3",
+ "react-redux": "^4.0.0",
+ "redux": "^3.0.4",
+ "redux-promise": "^0.5.0",
+ "redux-thunk": "^1.0.0",
"webpack": "^1.12.8"
},
"devDependencies": {
diff --git a/config/secrets.yml b/config/secrets.yml
index b234fc0..76ffeab 100644
--- a/config/secrets.yml
+++ b/config/secrets.yml
@@ -11,10 +11,10 @@
# if you're sharing your code publicly.
development:
- secret_key_base: 61253371a60c2e895b0f439a372de7175f88accf858b43c1f4638ae5fe1991d594fa2c7d6c5a13e385a96399f0386291b34a2f973d34f687f44480c07dc820a5
+ secret_key_base: 5393f8f843ef2a16a42aa4c037f1e4dc7ad36b2e7d7d3fe17439e27d05798d8e91d11d7c75d9968dad2b105ed50d0c69bb3cee166a731f2efcbb0c9ee38f3514
test:
- secret_key_base: 2fc9c95f86cd26ae15f34870dbfe4f4c65003347ef70c2e14074bf99f5c58f692a1aa6ad0ab8063eb2c33f09caad3cb1c06292ef29fe1e429100242d07c641d8
+ secret_key_base: 9bb808acd13cbef945156389ae83d3741c55dbc71ff1d53eb556df8f43c0084300eaf1abcc612410f774f0b142fa280c04ee109942c4636d55fe1a35fba36fec
# Do not keep production secrets in the repository,
# instead read values from the environment.