Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic to redux #153

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 0 additions & 45 deletions README.md

This file was deleted.

3 changes: 2 additions & 1 deletion bin/rails
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
3 changes: 2 additions & 1 deletion bin/rake
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as actionTypes from '../constants/helloWorldConstants';

export function updateName(name) {
return {
type: actionTypes.HELLO_WORLD_NAME_UPDATE,
name,
};
}
21 changes: 15 additions & 6 deletions client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 (
<div>
<h3>
Hello, {this.props.name}!
Hello, {name}!
</h3>
<p>
Say hello to:
<input type="text" value={this.props.name} onChange={this._handleChange} />
<input type="text" value={name} onChange={this._handleChange} />
</p>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
]);
42 changes: 26 additions & 16 deletions client/app/bundles/HelloWorld/containers/HelloWorld.jsx
Original file line number Diff line number Diff line change
@@ -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:
// <HelloWorldWidget $$helloWorldStore={$$helloWorldStore} actions={actions} />
return (
<div>
<HelloWorldWidget name={this.state.name} _updateName={this._updateName} />
</div>
<HelloWorldWidget {...{$$helloWorldStore, actions}} />
);
}
}

// 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);
21 changes: 21 additions & 0 deletions client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
14 changes: 14 additions & 0 deletions client/app/bundles/HelloWorld/reducers/index.jsx
Original file line number Diff line number Diff line change
@@ -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,
};
11 changes: 10 additions & 1 deletion client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx
Original file line number Diff line number Diff line change
@@ -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 = (
<HelloWorld {...props} />
<Provider store={store}>
<HelloWorld />
</Provider>
);
return reactComponent;
};
Expand Down
35 changes: 35 additions & 0 deletions client/app/bundles/HelloWorld/store/helloWorldStore.jsx
Original file line number Diff line number Diff line change
@@ -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;
};
20 changes: 20 additions & 0 deletions client/app/lib/middlewares/loggerMiddleware.js
Original file line number Diff line number Diff line change
@@ -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;
};
}
7 changes: 7 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions config/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down