├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── examples
├── .babelrc
├── DevTools.js
├── build
│ └── bundle.js
├── components
│ └── Form.js
├── index.html
├── index.js
├── package.json
├── reducers
│ └── FormReducer.js
├── utils
│ └── config.js
└── webpack.config.js
├── lib
└── index.js
├── package.json
├── src
└── index.js
└── test
└── test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": ["react"],
4 | "ecmaFeatures": {
5 | "jsx": true,
6 | "objectLiteralShorthandMethods": true
7 | },
8 | "env": {
9 | "browser": true,
10 | "node": true,
11 | "es6": true
12 | },
13 | "rules": {
14 | "quotes": [0],
15 | "new-parens": [1],
16 | "no-alert": [1],
17 | "handle-callback-err": [1],
18 | "strict": [1],
19 | "no-script-url": [0],
20 | "space-unary-ops": [0],
21 | "consistent-return": [0],
22 | "comma-dangle": 1,
23 | "no-mixed-requires": [1],
24 | "no-underscore-dangle": [0],
25 | "no-multi-spaces": [1],
26 | "no-unused-vars": [1],
27 | "key-spacing": [0],
28 | "no-empty": [1],
29 | "no-shadow": [0],
30 | "no-use-before-define": [0],
31 | "no-unused-expressions": [1],
32 | "no-new-func": [0],
33 | "new-cap": [0],
34 | "eqeqeq": [0],
35 | "curly": [0],
36 | "strict": [0],
37 | "no-new": [1],
38 | "eol-last": [1],
39 | "space-infix-ops": [1],
40 | "no-return-assign": [1],
41 | "comma-spacing": [1],
42 | "no-extra-boolean-cast": [1],
43 | "no-constant-condition": [1],
44 |
45 | "react/display-name": 0,
46 | "react/jsx-boolean-value": 1,
47 | "react/jsx-no-duplicate-props": 1,
48 | "react/jsx-no-undef": 1,
49 | "react/jsx-quotes": 1,
50 | "react/jsx-sort-prop-types": 0,
51 | "react/jsx-sort-props": 0,
52 | "react/jsx-uses-react": 1,
53 | "react/jsx-uses-vars": 1,
54 | "react/no-danger": 1,
55 | "react/no-did-mount-set-state": 0,
56 | "react/no-did-update-set-state": 1,
57 | "react/no-multi-comp": 1,
58 | "react/no-unknown-property": 1,
59 | "react/prop-types": 0,
60 | "react/react-in-jsx-scope": 0,
61 | "react/require-extension": 1,
62 | "react/self-closing-comp": 1,
63 | "react/sort-comp": 1,
64 | "react/wrap-multilines": 1
65 | },
66 | "globals": {
67 | "describe": false,
68 | "chai": false,
69 | "beforeEach": false,
70 | "afterEach": false,
71 | "it": false
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
28 | node_modules
29 |
30 | # Optional npm cache directory
31 | .npm
32 |
33 | # Optional REPL history
34 | .node_repl_history
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.0"
4 | script: "npm run test"
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Sen Yang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | redux-form-utils [](https://travis-ci.org/jasonslyvia/redux-form-utils) [](http://badge.fury.io/js/redux-form-utils)
2 | ==========================
3 |
4 | Make handling forms in Redux less painful by providing two helpful utility functions:
5 |
6 | - `createForm(options)`: return a [Higher Order Component](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775) which will pass all required form bindings (eg. `value`, `onChange` and more) to children
7 | - `bindRedux(options)`: return an object consists of four keys:
8 | - `state`: the initialState of the form
9 | - `reducer`: a reducer function handling form related actions
10 | - `actionCreators`: an object consists of two helpful action creators `clear(filed)` and `clearAll()`
11 | - `setInitValue`: a function to set initial value for form, useful when in `edit` mode
12 |
13 | [Live Demo](http://jasonslyvia.github.io/redux-form-utils/examples/)
14 |
15 | ## Why
16 |
17 | Suppose you have a form component in Redux app which consists of many `input[type=text]` and `select`s. In Redux, you have to give each input an `onChange` event handler, and handle the change action inside your reducers respectively.
18 |
19 | That might lead to a great load of redundant and duplicated code base.
20 |
21 | **Before**
22 |
23 | ```javascript
24 | class Form extends React.Component {
25 | handleChangeName(e) {
26 | this.props.changeName(e.target.value);
27 | }
28 |
29 | handleChangeAddress(e) {
30 | this.props.changeAddress(e.target.value);
31 | }
32 |
33 | handleChangeGender(e) {
34 | this.props.changeGender(e.target.value);
35 | }
36 |
37 | render() {
38 | return (
39 |
47 | );
48 | }
49 | }
50 | ```
51 |
52 | By using `redux-form-utils`, you're freed from all these repetitive work.
53 |
54 | **After**
55 |
56 | ```javascript
57 | import { createForm } from 'redux-form-utils';
58 |
59 | @createForm({
60 | form: 'my-form',
61 | fields: ['name', 'address', 'gender']
62 | })
63 | class Form extends React.Component {
64 | render() {
65 | // What is `this.props.fields`? This will be explained in the following docs.
66 | const { name, address, gender } = this.props.fields;
67 | return (
68 |
76 | );
77 | }
78 | }
79 | ```
80 |
81 | Notice how many lines of code have been reduced when you use `redux-form-utils`.
82 |
83 | That's why I create this.
84 |
85 | ## How about `redux-form`?
86 |
87 | It's great but it's too enormous, I just want a simple utility function to help me reduce repetitive work.
88 |
89 | ## Usage
90 |
91 | ```
92 | $ npm install --save redux-form-utils
93 | ```
94 |
95 | To completely make use of `redux-form-utils`, you have at least 2 steps to go.
96 |
97 | ### 1. Enhance your component
98 |
99 | First thing is you should enhance your component by using `createForm` function.
100 |
101 | In aforementioned example, I use this function as a [decorater](https://developer.mozilla.org/en-US/docs/Decorators). If it bugs you, you can switch to normal function paradigm.
102 |
103 | ```javascript
104 | import { createForm } from 'redux-form-utils';
105 |
106 | class Form extends React.Component {
107 | render() {
108 | const { name, address } = this.props.fields;
109 | return (
110 |
111 |
112 |
113 |
114 | );
115 | }
116 | }
117 |
118 | const EnhancedForm = createForm({
119 | form: 'my-form',
120 | fields: ['name', 'address']
121 | })(Form);
122 | ```
123 |
124 | By enhancing your component, it achieves 3 extra `props`:
125 |
126 | - `fields`(*Object*) An object contains fields you defined in `createForm` option, it looks like this `{fields: { name: { value: '', onChange: Function }}}`
127 | - `clear(field)`(*Function*) An action creator that will clear certain field
128 | - `clearAll()`(*Function*) An action creator to clear all fields in this form
129 |
130 | Then in your component's `render()` method, destructure these fields to form controls like `input`, `textarea` or `select`.
131 |
132 | ```javascript
133 | const { name } = this.props.fields;
134 | // Give `input` a `value` props and a `onChange` props
135 | ```
136 |
137 | At last, when you enhance your component, make sure it has Redux store's `dispatch` function as a props.
138 |
139 | Alternatively, you can connect your component using `react-redux`'s `connect` method, in this case `dispatch` is passed as props to your component too.
140 |
141 | ### 2. Enhance your reducer
142 |
143 | The second and the last thing to do is to enhance your reducer.
144 |
145 | Basically you should compose your form state to your reducer's `initialState`, and handle form actions in your reducer.
146 |
147 | ```javascript
148 | import { bindRedux } from 'redux-form-utils';
149 | const { state: formState , reducer: formReducer, actionCreators: formActionCreators } = bindRedux({
150 | form: 'my-form',
151 | fields: ['name', 'address']
152 | });
153 |
154 |
155 | // `formState` has a shape of:
156 | // {
157 | // form: {
158 | // name: {
159 | // value: '',
160 | // },
161 | // address: {
162 | // value: '',
163 | // }
164 | // }
165 | // }
166 |
167 | // Compose initialState with formState
168 | const initialState = {
169 | foo: 1,
170 | bar: 2,
171 | ...formState
172 | };
173 |
174 | function reducer(state = initialState, action) {
175 | switch (action.type) {
176 | case 'XXX_ACTION': {
177 | // Do sth for your own action
178 | }
179 |
180 | default:
181 | // Let formReducer handle default situation instead of returning state directly
182 | return formReducer(state, action);
183 | }
184 | }
185 | ```
186 |
187 | **Bonus**
188 |
189 | If you some redux flow control middleware like [redux-sequence-action](https://github.com/jasonslyvia/redux-sequence-action), you can make use of actionCreators returned by `bindRedux`. It's an object consists of two keys: `clear(field)` and `clearAll()`.
190 |
191 | So you can dispatch some action in sequence, for example send an AJAX request and then clear all form fields.
192 |
193 | ```js
194 | function add() {
195 | return [sendReqeust(), clearAll()];
196 | }
197 | ```
198 |
199 | ## Options
200 |
201 | Both `createForm` and `bindRedux` accept the same parameter: an object of your form's configuration.
202 |
203 | This object is in shape of:
204 |
205 | ### form
206 |
207 | Type: *String* Default: *undefined* Required: *true*
208 |
209 | A unique string key for your form.
210 |
211 | ### fields
212 |
213 | Type: *Array* Default: *[]* Required: *true*
214 |
215 | An array of form fields configuration.
216 |
217 | For the simple way, you can pass an array of strings.
218 |
219 | ```
220 | // Configure fields like this
221 | fields: ['name']
222 |
223 | // Get a props in your component like this
224 | {
225 | fields: {
226 | name: {
227 | value: '',
228 | onChange: Function
229 | }
230 | }
231 | }
232 |
233 | ```
234 |
235 | It's quite enough for normal `input` and `select`, but for composite React components, like a `Calendar` or `react-reselect`, `value` and `onChange` seems insufficient.
236 |
237 | So you can configure your field in an object as well:
238 |
239 | ```javascript
240 | // Configure fields like this
241 | fields: [{
242 | key: 'startDate',
243 | changeType: 'onSwitch',
244 | valueKey: 'date',
245 | // This resolver is called when your `onChange` callback (In this case, `onSwitch`) is called,
246 | // it will be called with excatly the same arguments provided to `onChange`, so you can resolve
247 | //the payload of what to change by your own
248 | resolver(date){
249 | return {
250 | date: date.focusedDate
251 | };
252 | }
253 | }]
254 |
255 | // Get a props in your component like this
256 | {
257 | fields: {
258 | startDate: {
259 | date: '',
260 | onSwitch: Function
261 | }
262 | }
263 | }
264 |
265 | // Use props in component like this
266 | const { startDate } = this.props.fields;
267 |
268 | ```
269 |
270 | ## Tips
271 |
272 | Since both `createForm` and `bindRedux` require the same option, it's wise to store these options into separate files and require them in your component and reducer.
273 |
274 | Check the [Live Demo](http://jasonslyvia.github.io/redux-form-utils/examples/) for more clue.
275 |
276 | ## Scripts
277 |
278 | ```
279 | $ npm run test
280 | ```
281 |
282 | ## License
283 |
284 | MIT
285 |
286 |
--------------------------------------------------------------------------------
/examples/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-0"],
3 | "env": {
4 | "development": {
5 | "plugins": [
6 | ["transform-decorators-legacy"],
7 | ]
8 | },
9 | "production": {
10 | "plugins": [
11 | ["transform-decorators-legacy"]
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevTools } from 'redux-devtools';
3 | import LogMonitor from 'redux-devtools-log-monitor';
4 | import DockMonitor from 'redux-devtools-dock-monitor';
5 |
6 | export default createDevTools(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/examples/components/Form.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createForm } from '../../lib/';
3 | import formConfig from '../utils/config';
4 |
5 | @createForm(formConfig)
6 | class Form extends React.Component {
7 | render() {
8 | const { clear, clearAll } = this.props;
9 | const { name, address, gender } = this.props.fields;
10 |
11 | return (
12 |