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 #206

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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ group :development do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
end

gem 'bootstrap-sass'
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import actionTypes from '../constants/helloWorldConstants';

export function updateName(name) {
return {
type: actionTypes.HELLO_WORLD_NAME_UPDATE,
name,
};
}
13 changes: 13 additions & 0 deletions client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// See https://www.npmjs.com/package/mirror-creator
// Allows us to set up constants in a slightly more concise syntax. See:
// client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx
import mirrorCreator from 'mirror-creator';

const actionTypes = mirrorCreator([
'HELLO_WORLD_NAME_UPDATE',
]);

// actionTypes = {HELLO_WORLD_NAME_UPDATE: "HELLO_WORLD_NAME_UPDATE"}
// Notice how we don't have to duplicate HELLO_WORLD_NAME_UPDATE twice
// thanks to mirror-creator.
export default actionTypes;
51 changes: 32 additions & 19 deletions client/app/bundles/HelloWorld/containers/HelloWorld.jsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
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 {
static propTypes = {
name: PropTypes.string.isRequired, // this is passed from the Rails view
dispatch: PropTypes.func.isRequired,

// This corresponds to the value used in function select above.
// 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
$$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired,
};

constructor(props, context) {
super(props, context);

// How to set initial state in ES6 class syntax
// https://facebook.github.io/react/docs/reusable-components.html#es6-classes
this.state = { name: this.props.name };

// 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');
}

updateName(name) {
this.setState({ name });
}

render() {
const { dispatch, $$helloWorldStore } = this.props;
const actions = bindActionCreators(helloWorldActionCreators, dispatch);
const { updateName } = actions;
const name = $$helloWorldStore.get('name');

// 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 {...{ updateName, name }} />
);
}
}

// 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);
19 changes: 19 additions & 0 deletions client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Immutable from 'immutable';

import 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,
};
14 changes: 12 additions & 2 deletions client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +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.
export default (props) => {
return (
<HelloWorld {...props} />
const store = createStore(props);
const reactComponent = (
<Provider store={store}>
<HelloWorld />
</Provider>
);
return reactComponent;
};
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,
}),
};

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;
};
}
6 changes: 6 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,21 @@
"babel-preset-stage-0": "^6.3.13",
"es5-shim": "^4.4.1",
"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.3",
"react-bootstrap": "^0.28.1",
"react-dom": "^0.14.3",
"react-on-rails": "2.0.1",
"react-redux": "^4.0.5",
"redux": "^3.0.5",
"redux-promise": "^0.5.0",
"redux-thunk": "^1.0.2",
"webpack": "^1.12.8"
},
"devDependencies": {
Expand Down