Skip to content

Commit 02ad06a

Browse files
committed
Merge branch 'master' into heroku
* master: (31 commits) upgraded redux-form to v1.4.0 upgraded redux-form to 1.3.4 rolled back last commit which prematurely tried to upgrade to "history" lib added history library upgraded to v2.0.0 of redux, react-redux and redux-devtools updated redux-form to v1.2.1 upgraded to redux-form 1.0.1 upgraded to redux-form 1.0.0 Fix styles not found in production bug moved Ducks doc to its own repo for findability. fixes #146 fixed readme error to fix #151 update react-hot-loader upgraded to redux-form v0.6.1 fixed bug in previous commit. oops. undid PR #93 to fix #142. thanks, @halt-hammerzeit! added the error handling to avoid http-party/node-http-proxy#527 updated paypal button updated paypal button added paypal button updated redux-form to 0.5.0 ...
2 parents 4d5bce0 + ce0bda3 commit 02ad06a

33 files changed

+377
-384
lines changed

README.md

+13-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![Demo on Heroku](https://img.shields.io/badge/demo-heroku-lightgrey.png)](https://react-redux.herokuapp.com)
66
[![Dependency Status](https://david-dm.org/erikras/react-redux-universal-hot-example.svg)](https://david-dm.org/erikras/react-redux-universal-hot-example)
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)
8+
[![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)
89

910
This is a starter boiler plate app I've put together using the following technologies:
1011

@@ -49,11 +50,15 @@ npm run start
4950

5051
## Demo
5152

52-
A demonstration of this app can be see [running on heroku](https://react-redux.herokuapp.com), which is a deployment of the [heroku branch](https://github.com/erikras/react-redux-universal-hot-example/tree/heroku).
53+
A demonstration of this app can be seen [running on heroku](https://react-redux.herokuapp.com), which is a deployment of the [heroku branch](https://github.com/erikras/react-redux-universal-hot-example/tree/heroku).
5354

5455
## Explanation
5556

56-
What initally gets run is `babel.server.js`, which does little more than enable ES6 and ES7 awesomeness in the server-side node code. It then initiates `server.js`. In `server.js` we proxy any requests to `/api/*` to the [API server](#api-server), running at `localhost:3030`. All the data fetching calls from the client go to `/api/*`. Aside from serving the favicon and static content from `/static`, the only thing `server.js` does is initiate delegate rendering to `react-router`. At the bottom of `server.js`, we listen to port `3000` and initiate the API server.
57+
What initally gets run is `bin/server.js`, which does little more than enable ES6 and ES7 awesomeness in the
58+
server-side node code. It then initiates `server.js`. In `server.js` we proxy any requests to `/api/*` to the
59+
[API server](#api-server), running at `localhost:3030`. All the data fetching calls from the client go to `/api/*`.
60+
Aside from serving the favicon and static content from `/static`, the only thing `server.js` does is initiate delegate
61+
rendering to `react-router`. At the bottom of `server.js`, we listen to port `3000` and initiate the API server.
5762

5863
#### Routing and HTML return
5964

@@ -80,6 +85,12 @@ The middleware, [`clientMiddleware.js`](https://github.com/erikras/react-redux-u
8085
1. To allow the action creators access to the client API facade. Remember this is the same on both the client and the server, and cannot simply be `import`ed because it holds the cookie needed to maintain session on server-to-server requests.
8186
2. To allow some actions to pass a "promise generator", a function that takes the API client and returns a promise. Such actions require three action types, the `REQUEST` action that initiates the data loading, and a `SUCCESS` and `FAILURE` action that will be fired depending on the result of the promise. There are other ways to accomplish this, some discussed [here](https://github.com/gaearon/redux/issues/99), which you may prefer, but to the author of this example, the middleware way feels cleanest.
8287

88+
#### What the Duck?
89+
90+
[Ducks](https://github.com/erikras/ducks-modular-redux) are a Redux Style Proposal that I came up with to better
91+
isolate concerns within a Redux application. I encourage you to read the
92+
[Ducks Docs](https://github.com/erikras/ducks-modular-redux) and provide feedback.
93+
8394
#### API Server
8495

8596
This is where the meat of your server-side application goes. It doesn't have to be implemented in Node or Express at all. This is where you connect to your database and provide authentication and session management. In this example, it's just spitting out some json with the current time stamp.

docs/Ducks.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This document has found [another, hopefully permanent, home](https://github.com/erikras/ducks-modular-redux).
2+
3+
Quack.

karma.conf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.exports = function (config) {
88
singleRun: true,
99

1010
frameworks: [ 'mocha' ],
11-
11+
1212
files: [
1313
'tests.webpack.js'
1414
],

package.json

+8-7
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,23 @@
6666
"file-loader": "^0.8.4",
6767
"http-proxy": "^1.11.1",
6868
"lru-memoize": "0.0.2",
69+
"map-props": "^0.1.1",
6970
"piping": "0.2.0",
7071
"pretty-error": "^1.1.2",
7172
"query-string": "^2.4.0",
7273
"react": "0.13.3",
7374
"react-document-meta": "^0.1.4",
7475
"react-inline-css": "1.2.1",
75-
"react-redux": "0.9.0",
76-
"react-router": "v1.0.0-beta3",
77-
"redux": "^1.0.1",
78-
"redux-form": "^0.2.4",
76+
"react-redux": "^2.0.0",
77+
"react-router": "v1.0.0-beta2",
78+
"redux": "^2.0.0",
79+
"redux-form": "^1.4.0",
7980
"serialize-javascript": "^1.0.0",
8081
"serve-favicon": "^2.3.0",
8182
"serve-static": "^1.10.0",
8283
"superagent": "^1.2.0",
8384
"url-loader": "^0.5.6",
84-
"webpack-isomorphic-tools": "^0.8.1"
85+
"webpack-isomorphic-tools": "^0.8.5"
8586
},
8687
"devDependencies": {
8788
"autoprefixer-loader": "^2.0.0",
@@ -110,8 +111,8 @@
110111
"mocha": "^2.2.5",
111112
"node-sass": "^3.2.0",
112113
"react-a11y": "0.2.6",
113-
"react-hot-loader": "1.2.8",
114-
"redux-devtools": "1.0.2",
114+
"react-hot-loader": "1.3.0",
115+
"redux-devtools": "^2.0.0",
115116
"sass-loader": "^2.0.0",
116117
"strip-loader": "^0.1.0",
117118
"style-loader": "^0.12.3",

src/actions/actionTypes.js

-21
This file was deleted.

src/actions/authActions.js

-36
This file was deleted.

src/actions/counterActions.js

-9
This file was deleted.

src/actions/infoActions.js

-12
This file was deleted.

src/actions/widgetActions.js

-35
This file was deleted.

src/components/CounterButton.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {Component, PropTypes} from 'react';
22
import {bindActionCreators} from 'redux';
33
import {connect} from 'react-redux';
4-
import {increment} from '../actions/counterActions';
4+
import {increment} from '../ducks/counter';
55

66
@connect(
77
state => ({count: state.counter.count}),

src/components/InfoBar.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {Component, PropTypes} from 'react';
22
import {bindActionCreators} from 'redux';
33
import {connect} from 'react-redux';
4-
import {load} from '../actions/infoActions';
4+
import {load} from '../ducks/info';
55

66
@connect(
77
state => ({info: state.info.data}),

src/components/SurveyForm.js

+45-58
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, {Component, PropTypes} from 'react';
2-
import {connect} from 'react-redux';
3-
import reduxForm from 'redux-form';
2+
import {connectReduxForm} from 'redux-form';
43
import surveyValidation from '../validation/surveyValidation';
4+
import mapProps from 'map-props';
55

6-
function asyncValidator(data) {
6+
function asyncValidate(data) {
77
// TODO: figure out a way to move this to the server. need an instance of ApiClient
88
if (!data.email) {
99
return Promise.resolve({valid: true});
@@ -20,87 +20,70 @@ function asyncValidator(data) {
2020
});
2121
}
2222

23-
@connect(state => ({
24-
form: state.surveyForm
25-
}))
26-
@reduxForm('surveyForm', surveyValidation).async(asyncValidator, 'email')
23+
@connectReduxForm({
24+
form: 'survey',
25+
fields: ['name', 'email', 'occupation'],
26+
validate: surveyValidation,
27+
asyncValidate,
28+
asyncBlurFields: ['email']
29+
})
2730
export default
2831
class SurveyForm extends Component {
2932
static propTypes = {
3033
asyncValidating: PropTypes.bool.isRequired,
31-
data: PropTypes.object.isRequired,
34+
fields: PropTypes.object.isRequired,
3235
dirty: PropTypes.bool.isRequired,
33-
errors: PropTypes.object.isRequired,
3436
handleBlur: PropTypes.func.isRequired,
3537
handleChange: PropTypes.func.isRequired,
3638
handleSubmit: PropTypes.func.isRequired,
39+
resetForm: PropTypes.func.isRequired,
3740
invalid: PropTypes.bool.isRequired,
3841
pristine: PropTypes.bool.isRequired,
39-
touched: PropTypes.object.isRequired,
4042
valid: PropTypes.bool.isRequired
4143
}
4244

4345
render() {
4446
const {
45-
data: {name, email, occupation},
46-
errors: {name: nameError, email: emailError, occupation: occupationError},
47-
touched: {name: nameTouched, email: emailTouched, occupation: occupationTouched},
4847
asyncValidating,
49-
handleBlur,
50-
handleChange,
48+
dirty,
49+
fields: {name, email, occupation},
50+
active,
5151
handleSubmit,
52-
valid,
5352
invalid,
53+
resetForm,
5454
pristine,
55-
dirty
55+
valid
5656
} = this.props;
57+
const styles = require('./SurveyForm.scss');
58+
const renderInput = (field, label, showAsyncValidating) =>
59+
<div className={'form-group' + (field.error && field.touched ? ' has-error' : '')}>
60+
<label htmlFor={field.name} className="col-sm-2">{label}</label>
61+
<div className={'col-sm-8 ' + styles.inputGroup}>
62+
{showAsyncValidating && asyncValidating && <i className={'fa fa-cog fa-spin ' + styles.cog}/>}
63+
<input type="text" className="form-control" id={field.name} {...field}/>
64+
{field.error && field.touched && <div className="text-danger">{field.error}</div>}
65+
<div className={styles.flags}>
66+
{field.dirty && <span className={styles.dirty} title="Dirty">D</span>}
67+
{field.active && <span className={styles.active} title="Active">A</span>}
68+
{field.visited && <span className={styles.visited} title="Visited">V</span>}
69+
{field.touched && <span className={styles.touched} title="Touched">T</span>}
70+
</div>
71+
</div>
72+
</div>;
73+
5774
return (
5875
<div>
5976
<form className="form-horizontal" onSubmit={handleSubmit}>
60-
<div className={'form-group' + (nameError && nameTouched ? ' has-error' : '')}>
61-
<label htmlFor="name" className="col-sm-2">Full Name</label>
62-
63-
<div className="col-sm-10">
64-
<input type="text"
65-
className="form-control"
66-
id="name"
67-
value={name}
68-
onChange={handleChange('name')}
69-
onBlur={handleBlur('name')}/>
70-
{nameError && nameTouched && <div className="text-danger">{nameError}</div>}
71-
</div>
72-
</div>
73-
<div className={'form-group' + (emailError && emailTouched ? ' has-error' : '')}>
74-
<label htmlFor="email" className="col-sm-2">Email address</label>
75-
76-
<div className="col-sm-10">
77-
<input type="email"
78-
className="form-control"
79-
id="email"
80-
value={email}
81-
onChange={handleChange('email')}
82-
onBlur={handleBlur('email')}/>
83-
{emailError && emailTouched && <div className="text-danger">{emailError}</div>}
84-
{asyncValidating && <div>Validating...</div>}
85-
</div>
86-
</div>
87-
<div className={'form-group' + (occupationError && occupationTouched ? ' has-error' : '')}>
88-
<label htmlFor="occupation" className="col-sm-2">Occupation</label>
89-
90-
<div className="col-sm-10">
91-
<input type="text"
92-
className="form-control"
93-
id="occupation"
94-
value={occupation}
95-
onChange={handleChange('occupation')}
96-
onBlur={handleBlur('occupation')}/>
97-
{occupationError && occupationTouched && <div className="text-danger">{occupationError}</div>}
98-
</div>
99-
</div>
77+
{renderInput(name, 'Full Name')}
78+
{renderInput(email, 'Email', true)}
79+
{renderInput(occupation, 'Occupation')}
10080
<div className="form-group">
10181
<div className="col-sm-offset-2 col-sm-10">
10282
<button className="btn btn-success" onClick={handleSubmit}>
103-
<i className="fa fa-paper-airplane"/> Submit
83+
<i className="fa fa-paper-plane"/> Submit
84+
</button>
85+
<button className="btn btn-warning" onClick={resetForm} style={{marginLeft:15}}>
86+
<i className="fa fa-undo"/> Reset
10487
</button>
10588
</div>
10689
</div>
@@ -110,6 +93,10 @@ class SurveyForm extends Component {
11093

11194
<table className="table table-striped">
11295
<tbody>
96+
<tr>
97+
<th>Active Field</th>
98+
<td>{active}</td>
99+
</tr>
113100
<tr>
114101
<th>Dirty</th>
115102
<td className={dirty ? 'success' : 'danger'}>{dirty ? 'true' : 'false'}</td>

0 commit comments

Comments
 (0)