├── .editorconfig
├── .gitignore
├── .prettierrc
├── .travis.yml
├── HISTORY.md
├── LICENSE.md
├── README.md
├── examples
├── across-router.html
├── across-router.js
├── async-init.html
├── async-init.js
├── dynamic-fields.html
├── dynamic-fields.js
├── dynamic.html
├── dynamic.js
├── file-input.html
├── file-input.js
├── getFieldDecorator.html
├── getFieldDecorator.js
├── input-array.html
├── input-array.js
├── modal.html
├── modal.js
├── nested-field.html
├── nested-field.js
├── normalize.html
├── normalize.js
├── overview.html
├── overview.js
├── parallel-form.html
├── parallel-form.js
├── promise-validate.html
├── promise-validate.js
├── react-native
│ ├── App.js
│ └── expo.jpg
├── redux.html
├── redux.js
├── server-validate.html
├── server-validate.js
├── setFieldsValue.html
├── setFieldsValue.js
├── start-end-date.html
├── start-end-date.js
├── styles.js
├── suggest.html
├── suggest.js
├── validateFirst.html
├── validateFirst.js
├── validateTrigger.html
└── validateTrigger.js
├── index.js
├── package.json
├── scripts
└── index.js
├── src
├── FieldElemWrapper.js
├── createBaseForm.js
├── createDOMForm.js
├── createFieldsStore.js
├── createForm.js
├── createFormField.js
├── index.js
├── propTypes.js
└── utils.js
└── tests
├── FieldElemWrapper.spec.js
├── async-validation.spec.js
├── clean-field.spec.js
├── createDOMForm.spec.js
├── createForm.spec.js
├── dynamic-binding.spec.js
├── dynamic-rule.spec.js
├── form.spec.js
├── getFieldDecorator.spec.js
├── getFieldProps.spec.js
├── index.js
├── overview.spec.js
├── setup.js
├── switch-field.spec.js
├── utils.spec.js
├── validateArray.spec.js
├── validateFields.spec.js
└── validateFieldsAndScroll.spec.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # Unix-style newlines with a newline ending every file
5 | [*.{js,css}]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.log
3 | .idea
4 | .ipr
5 | .iws
6 | *~
7 | ~*
8 | *.diff
9 | *.patch
10 | *.bak
11 | .DS_Store
12 | Thumbs.db
13 | .project
14 | .*proj
15 | .svn
16 | *.swp
17 | *.swo
18 | *.pyc
19 | *.pyo
20 | node_modules
21 | .cache
22 | *.css
23 | build
24 | dist
25 | lib
26 | coverage
27 | yarn.lock
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": true,
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | "trailingComma": "all"
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | sudo: false
4 |
5 | node_js:
6 | - 11
7 |
8 | script:
9 | - |
10 | if [ "$TEST_TYPE" = test ]; then
11 | npm test
12 | else
13 | npm run $TEST_TYPE
14 | fi
15 | env:
16 | matrix:
17 | - TEST_TYPE=lint
18 | - TEST_TYPE=test
19 | - TEST_TYPE=coverage
20 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # History
2 | ----
3 |
4 | ## 2.4.10 / 2019-11-09
5 |
6 | - There should not be any lifecycle warning now!
7 |
8 | ## 2.4.0 / 2018-12-21
9 |
10 | - Field option add `preserve` to enable you keep value even if field removed.
11 |
12 | ## 2.3.0 / 2018-12-17
13 |
14 | - `createForm` add `name` prop. It will be as prefix with `fieldNameProp`
15 |
16 | ## 2.2.1 / 2018-07-11
17 |
18 | - `validateFieldsAndScroll` should ignore `input[type=hidden]` [#169](https://github.com/react-component/form/pull/169) [@HsuTing](https://github.com/HsuTing)
19 | - `mapPropsToFields` and so on support field with `null` value [#159](https://github.com/react-component/form/pull/159) [@normanrz](https://github.com/normanrz)
20 |
21 | ## 2.2.0 / 2018-04-04
22 |
23 | - Support callback in setFieldsValue [#144](https://github.com/react-component/form/pull/144)
24 | - Expose formShape [#154](https://github.com/react-component/form/pull/154) [@sylvainar](https://github.com/sylvainar)
25 | - Fix bug with disordered array [#143](https://github.com/react-component/form/pull/143)
26 |
27 | ## 2.1.0 / 2017-11-22
28 |
29 | - Support switching from different components with same field name. [#117](https://github.com/react-component/form/pull/117)
30 |
31 | ## 2.0.0 / 2017-11-07
32 |
33 | - Remove `option.exclusive` of `getFieldProps` and `getFieldDecorator`, just use something like [`antd.Radio.Group`](https://ant.design/components/radio/#components-radio-demo-radiogroup) or [`antd.Checkbox.Group`](https://ant.design/components/checkbox/#components-checkbox-demo-group) as workaround.
34 | - Add `createFormField`, and you must use it to wrap field data in `option.mapPropsToFields` of `createForm` or `createDOMForm`:
35 | Before rc-form@2.0.0:
36 | ```jsx
37 | import { createForm } from 'rc-form';
38 | createFrom({
39 | mapPropsToFields() {
40 | return {
41 | name: { value: 'rc-form' },
42 | };
43 | },
44 | })
45 | ```
46 | After rc-form@2.0.0:
47 | ```jsx
48 | import { createForm, createFormField } from 'rc-form';
49 | createFrom({
50 | mapPropsToFields() {
51 | return {
52 | name: createFormField({ value: 'rc-form' }),
53 | };
54 | },
55 | })
56 | ```
57 | - Deprecate `form.isSubmitting` and `form.submit`, just handle submit status in your own code.
58 |
59 |
60 | ## 1.4.0 / 2017-06-13
61 |
62 | - support wrappedComponentRef and deprecate withRef [#87](https://github.com/react-component/form/pull/87)
63 |
64 | ## 1.3.0 / 2017-01-07
65 |
66 | - support touch checking: https://github.com/react-component/form/pull/56
67 |
68 | ## 1.2.0 / 2017-01-05
69 |
70 | - support onValuesChange: https://github.com/react-component/form/pull/55
71 |
72 | ## 1.1.0 / 2016-12-28
73 |
74 | - support nested field: https://github.com/react-component/form/pull/48
75 |
76 | ## 1.0.0 / 2016-08-29
77 |
78 | - support getFieldDecorator. stable.
79 |
80 | ## 0.17.0 / 2016-06-12
81 |
82 | - support checkbox radio https://github.com/react-component/form/pull/21
83 | - add exclusive config
84 |
85 | ## 0.16.0 / 2016-05-19
86 |
87 | - move instance to this.instances
88 |
89 | ## 0.15.0 / 2016-03-28
90 |
91 | - add getValueFromEvent/getValueProps
92 |
93 |
94 | ## 0.14.0 / 2016-02-27
95 |
96 | - remove refComponent prop.(defaults to true), so you must use getFieldInstance method to get instance instead of ref
97 |
98 | ## 0.13.0 / 2016-02-14
99 |
100 | - support rc-form/lib/createDOMForm
101 |
102 | ## 0.12.0 / 2016-02-02
103 |
104 | - support refComponent/mapProps option for createForm to scroll error fields into view.
105 |
106 | ## 0.11.0 / 2016-02-02
107 |
108 | - support validateMessages of createForm option.
109 |
110 | ## 0.10.0 / 2016-01-27
111 |
112 | - support setFieldsInitialValue/submit/isFieldsValidating/isSubmitting method for this.props.form
113 |
114 | ## 0.9.0 / 2016-01-18
115 |
116 | - support force, force to revalidate.
117 |
118 | ```
119 | this.props.validateFields(['xx'], {force: true}).
120 | ```
121 |
122 | ## 0.8.0 / 2016-01-13
123 |
124 | - support validate/validateFirst option for getFieldProps
125 |
126 | ## 0.7.0 / 2015-12-29
127 |
128 | - support this.props.form.resetFields
129 |
130 | ## 0.6.0 / 2015-12-28
131 |
132 | - support normalize in getFieldProps option
133 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-present yiminghe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rc-form
2 |
3 | React High Order Form Component.
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![build status][travis-image]][travis-url]
7 | [![Test coverage][coveralls-image]][coveralls-url]
8 | [![gemnasium deps][gemnasium-image]][gemnasium-url]
9 | [![node version][node-image]][node-url]
10 | [![npm download][download-image]][download-url]
11 | [![Code Quality: Javascript][lgtm-badge]][lgtm-badge-url]
12 | [![Total alerts][lgtm-alerts]][lgtm-alerts-url]
13 |
14 | [npm-image]: http://img.shields.io/npm/v/rc-form.svg?style=flat-square
15 | [npm-url]: http://npmjs.org/package/rc-form
16 | [travis-image]: https://img.shields.io/travis/react-component/form.svg?style=flat-square
17 | [travis-url]: https://travis-ci.org/react-component/form
18 | [coveralls-image]: https://img.shields.io/coveralls/react-component/form.svg?style=flat-square
19 | [coveralls-url]: https://coveralls.io/r/react-component/form?branch=master
20 | [gemnasium-image]: http://img.shields.io/gemnasium/react-component/form.svg?style=flat-square
21 | [gemnasium-url]: https://gemnasium.com/react-component/form
22 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square
23 | [node-url]: http://nodejs.org/download/
24 | [download-image]: https://img.shields.io/npm/dm/rc-form.svg?style=flat-square
25 | [download-url]: https://npmjs.org/package/rc-form
26 | [lgtm-badge]: https://img.shields.io/lgtm/grade/javascript/g/react-component/form.svg?logo=lgtm&logoWidth=18
27 | [lgtm-badge-url]: https://lgtm.com/projects/g/react-component/form/context:javascript
28 | [lgtm-alerts]: https://img.shields.io/lgtm/alerts/g/react-component/form.svg?logo=lgtm&logoWidth=18
29 | [lgtm-alerts-url]: https://lgtm.com/projects/g/react-component/form/alerts
30 |
31 | ## Development
32 |
33 | ```
34 | npm install
35 | npm start
36 | open http://localhost:8000/examples/
37 | ```
38 |
39 | ## Feature
40 |
41 | * Support react.js and even react-native
42 | * Validate fields with [async-validator](https://github.com/yiminghe/async-validator/)
43 |
44 | ## Install
45 |
46 | [](https://npmjs.org/package/rc-form)
47 |
48 | ## Usage
49 |
50 | ```js
51 | import { createForm, formShape } from 'rc-form';
52 |
53 | class Form extends React.Component {
54 | static propTypes = {
55 | form: formShape,
56 | };
57 |
58 | submit = () => {
59 | this.props.form.validateFields((error, value) => {
60 | console.log(error, value);
61 | });
62 | }
63 |
64 | render() {
65 | let errors;
66 | const { getFieldProps, getFieldError } = this.props.form;
67 | return (
68 |
this.formRef = inst} />
159 | this.formRef // => The instance of Form
160 | ```
161 |
162 | ## (WrappedComponent: React.Component) => React.Component
163 |
164 | The returned function of createForm(). It will pass an object as prop `form` with the following members to WrappedComponent:
165 |
166 | ### getFieldProps(name, option): Object { [valuePropName], [trigger], [validateTrigger] }
167 |
168 | Will create props which can be set on a input/InputComponent which support value and onChange interface.
169 |
170 | After set, this will create a binding with this input.
171 |
172 | ```jsx
173 |
176 | ```
177 |
178 | #### name: String
179 |
180 | This input's unique name.
181 |
182 | #### option: Object
183 |
184 | | Option | Description | Type | Default |
185 | |-----------|------------------------------------------|------------|---------|
186 | | option.valuePropName | Prop name of component's value field, eg: checkbox should be set to `checked` ... | String | 'value' |
187 | | option.getValueProps | Get the component props according to field value. | (value): Object | (value) => ({ value }) |
188 | | option.getValueFromEvent | Specify how to get value from event. | (e): any | See below |
189 | | option.initialValue | Initial value of current component. | any | - |
190 | | option.normalize | Return normalized value. | (value, prev, all): Object | - |
191 | | option.trigger | Event which is listened to collect form data. | String | 'onChange' |
192 | | option.validateTrigger | Event which is listened to validate. Set to falsy to only validate when call props.validateFields. | String|String[] | 'onChange' |
193 | | option.rules | Validator rules. see: [async-validator](https://github.com/yiminghe/async-validator) | Object[] | - |
194 | | option.validateFirst | Whether stop validate on first rule of error for this field. | boolean | false |
195 | | option.validate | | Object[] | - |
196 | | option.validate[n].trigger | Event which is listened to validate. Set to falsy to only validate when call props.validateFields. | String|String[] | 'onChange' |
197 | | option.validate[n].rules | Validator rules. see: [async-validator](https://github.com/yiminghe/async-validator) | Object[] | - |
198 | | option.hidden | Ignore current field while validating or gettting fields | boolean | false |
199 | | option.preserve | Whether to preserve the value. That will remain the value when the field be unmounted and be mounted again | boolean | false |
200 |
201 | ##### Default value of `getValueFromEvent`
202 |
203 | ```js
204 | function defaultGetValueFromEvent(e) {
205 | if (!e || !e.target) {
206 | return e;
207 | }
208 | const { target } = e;
209 | return target.type === 'checkbox' ? target.checked : target.value;
210 | }
211 | ```
212 |
213 | ##### Tips
214 |
215 | ```js
216 | {
217 | validateTrigger: 'onBlur',
218 | rules: [{required: true}],
219 | }
220 | // is the shorthand of
221 | {
222 | validate: [{
223 | trigger: 'onBlur',
224 | rules: [{required: true}],
225 | }],
226 | }
227 | ```
228 |
229 | ### getFieldDecorator(name:String, option: Object) => (React.Node) => React.Node
230 |
231 | Similar to `getFieldProps`, but add some helper warnings and you can write onXX directly inside React.Node props:
232 |
233 | ```jsx
234 |
237 | ```
238 |
239 | ### getFieldsValue([fieldNames: String[]])
240 |
241 | Get fields value by fieldNames.
242 |
243 | ### getFieldValue(fieldName: String)
244 |
245 | Get field value by fieldName.
246 |
247 | ### getFieldInstance(fieldName: String)
248 |
249 | Get field react public instance by fieldName.
250 |
251 | ### setFieldsValue(obj: Object)
252 |
253 | Set fields value by kv object.
254 |
255 | ### setFieldsInitialValue(obj: Object)
256 |
257 | Set fields initialValue by kv object. use for reset and initial display/value.
258 |
259 | ### setFields(obj: Object)
260 |
261 | Set fields by kv object. each field can contain errors and value member.
262 |
263 | ### validateFields([fieldNames: String[]], [options: Object], callback: (errors, values) => void)
264 |
265 | Validate and get fields value by fieldNames.
266 |
267 | options is the same as validate method of [async-validator](https://github.com/yiminghe/async-validator).
268 | And add `force`.
269 |
270 | #### options.force: Boolean
271 |
272 | Defaults to false. Whether to validate fields which have been validated(caused by validateTrigger).
273 |
274 | ### getFieldsError(names): Object{ [name]: String[] }
275 |
276 | Get inputs' validate errors.
277 |
278 | ### getFieldError(name): String[]
279 |
280 | Get input's validate errors.
281 |
282 | ### isFieldValidating(name: String): Bool
283 |
284 | Whether this input is validating.
285 |
286 | ### isFieldsValidating(names: String[]): Bool
287 |
288 | Whether one of the inputs is validating.
289 |
290 | ### isFieldTouched(name: String): Bool
291 |
292 | Whether this input's value had been changed by user.
293 |
294 | ### isFieldsTouched(names: String[]): Bool
295 |
296 | Whether one of the inputs' values had been changed by user.
297 |
298 | ### resetFields([names: String[]])
299 |
300 | Reset specified inputs. Defaults to all.
301 |
302 | ### isSubmitting(): Bool (Deprecated)
303 |
304 | Whether the form is submitting.
305 |
306 | ### submit(callback: Function) (Deprecated)
307 |
308 | Cause isSubmitting to return true, after callback called, isSubmitting return false.
309 |
310 |
311 | ## rc-form/lib/createDOMForm(option): Function
312 |
313 | createDOMForm enhancement, support props.form.validateFieldsAndScroll
314 |
315 | ### validateFieldsAndScroll([fieldNames: String[]], [options: Object], callback: (errors, values) => void)
316 |
317 | props.form.validateFields enhancement, support scroll to the first invalid form field, `scroll` is the same as [dom-scroll-into-view's function parameter `config`](https://github.com/yiminghe/dom-scroll-into-view#function-parameter).
318 |
319 | #### options.container: HTMLElement
320 |
321 | Defaults to first scrollable container of form field(until document).
322 |
323 |
324 | ## Notes
325 |
326 | - Do not use stateless function component inside Form component: https://github.com/facebook/react/pull/6534
327 |
328 | - you can not set same prop name as the value of validateTrigger/trigger for getFieldProps
329 |
330 | ```js
331 |
333 | })}>
334 | ```
335 |
336 | - you can not use ref prop for getFieldProps
337 |
338 | ```js
339 |
340 |
341 | this.props.form.getFieldInstance('ref') // use this to get ref
342 | ```
343 |
344 | or
345 |
346 | ```js
347 | (only allow function)
349 | })} />
350 | ```
351 |
352 | ## Test Case
353 |
354 | ```
355 | npm test
356 | npm run chrome-test
357 | ```
358 |
359 | ## Coverage
360 |
361 | ```
362 | npm run coverage
363 | ```
364 |
365 | open coverage/ dir
366 |
367 | ## License
368 |
369 | rc-form is released under the MIT license.
370 |
--------------------------------------------------------------------------------
/examples/across-router.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/across-router.html
--------------------------------------------------------------------------------
/examples/across-router.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import React from 'react';
4 | import PropTypes from 'prop-types';
5 | import ReactDOM from 'react-dom';
6 | import { createForm, formShape } from 'rc-form';
7 | import { Router, Route, hashHistory } from 'react-router';
8 | import { regionStyle } from './styles';
9 |
10 | class ChildForm extends React.Component {
11 | static propTypes = {
12 | initialValue: PropTypes.object,
13 | form: formShape,
14 | onDestroy: PropTypes.func,
15 | };
16 | componentWillUnmount() {
17 | this.props.onDestroy(this.props.form.getFieldsValue());
18 | }
19 | onClick = () => {
20 | window.history.back();
21 | }
22 | render() {
23 | const { getFieldProps } = this.props.form;
24 | return (
34 |
child form
35 |
36 | name:
37 |
41 |
42 |
43 |
44 |
45 |
);
46 | }
47 | }
48 |
49 | ChildForm = createForm()(ChildForm);
50 |
51 | class Picker extends React.Component {
52 | static propTypes = {
53 | childForm: PropTypes.object,
54 | onChange: PropTypes.func,
55 | value: PropTypes.object,
56 | };
57 | onClick = () => {
58 | window.location.hash = '/open';
59 | }
60 | render() {
61 | const { value, childForm } = this.props;
62 | const valueEl = value ?
63 | {value.name}
:
64 | please select
;
65 | return (
66 | {valueEl}
67 | {childForm ? React.cloneElement(childForm, {
68 | onDestroy: this.props.onChange,
69 | initialValue: value || {},
70 | }) : null}
71 |
);
72 | }
73 | }
74 |
75 | class ParentForm extends React.Component {
76 | static propTypes = {
77 | initialValue: PropTypes.object,
78 | form: formShape,
79 | children: PropTypes.any,
80 | };
81 | onClick = () => {
82 | console.log(this.props.form.getFieldsValue());
83 | }
84 | render() {
85 | const { getFieldProps } = this.props.form;
86 | return (
87 |
parent form
88 |
89 | family:
90 |
91 |
94 |
95 |
96 |
97 |
98 |
);
99 | }
100 | }
101 |
102 | ParentForm = createForm()(ParentForm);
103 |
104 | ReactDOM.render((
110 |
111 |
112 |
113 |
114 |
115 |
), document.getElementById('__react-content'));
116 |
--------------------------------------------------------------------------------
/examples/async-init.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/async-init.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { regionStyle, errorStyle } from './styles';
7 |
8 | class Email extends React.Component {
9 | static propTypes = {
10 | form: formShape,
11 | };
12 |
13 | checkSpecial = (rule, value, callback) => {
14 | setTimeout(() => {
15 | if (value === 'yiminghe@gmail.com') {
16 | callback('can not be!');
17 | } else {
18 | callback();
19 | }
20 | }, 1000);
21 | }
22 |
23 | render() {
24 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
25 | const errors = getFieldError('email');
26 | return (
27 |
email validate onBlur
28 |
29 |
45 |
46 | {errors ? errors.join(',') : null}
47 |
48 |
49 | {isFieldValidating('email') ? 'validating' : null}
50 |
51 |
);
52 | }
53 | }
54 |
55 | class Form extends Component {
56 | static propTypes = {
57 | form: formShape,
58 | };
59 |
60 | state = {
61 | loading: true,
62 | };
63 |
64 | componentDidMount() {
65 | setTimeout(() => {
66 | this.setState({
67 | loading: false,
68 | }, () => {
69 | setTimeout(() => {
70 | this.props.form.setFieldsInitialValue({
71 | email: 'xx@gmail.com',
72 | });
73 | }, 1000);
74 | });
75 | }, 1000);
76 | }
77 |
78 | onSubmit = (e) => {
79 | e.preventDefault();
80 | this.props.form.submit((callback) => {
81 | setTimeout(() => {
82 | this.props.form.validateFields((error, values) => {
83 | if (!error) {
84 | console.log('ok', values);
85 | } else {
86 | console.log('error', error, values);
87 | }
88 | callback();
89 | });
90 | }, 1000);
91 | });
92 | }
93 |
94 | reset = (e) => {
95 | e.preventDefault();
96 | this.props.form.resetFields();
97 | }
98 |
99 | render() {
100 | if (this.state.loading) {
101 | return loading;
102 | }
103 | const { form } = this.props;
104 | const disabled = form.isFieldsValidating() || form.isSubmitting();
105 | return (
106 |
async init field
107 |
116 |
);
117 | }
118 | }
119 |
120 | const NewForm = createForm()(Form);
121 |
122 | ReactDOM.render(, document.getElementById('__react-content'));
123 |
--------------------------------------------------------------------------------
/examples/dynamic-fields.html:
--------------------------------------------------------------------------------
1 | placeholder
2 |
--------------------------------------------------------------------------------
/examples/dynamic-fields.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { createForm, formShape } from 'rc-form';
6 |
7 | class Form1 extends React.Component {
8 | static propTypes = {
9 | form: formShape,
10 | };
11 | state = {
12 | useInput: true,
13 | };
14 | onSubmit = (e) => {
15 | e.preventDefault();
16 | this.props.form.validateFields((error, values) => {
17 | if (!error) {
18 | console.log('ok', values);
19 | } else {
20 | console.log('error', error, values);
21 | }
22 | });
23 | };
24 | changeUseInput = (e) => {
25 | this.setState({
26 | useInput: e.target.checked,
27 | });
28 | }
29 | render() {
30 | const { getFieldError, getFieldDecorator } = this.props.form;
31 |
32 | return (
33 |
59 | );
60 | }
61 | }
62 |
63 | class Form2 extends React.Component {
64 | static propTypes = {
65 | form: formShape,
66 | };
67 | state = {
68 | useInput: true,
69 | };
70 | componentWillMount() {
71 | const { getFieldDecorator } = this.props.form;
72 | this.nameDecorator = getFieldDecorator('name', {
73 | initialValue: '',
74 | rules: [{
75 | required: true,
76 | message: 'What\'s your name?',
77 | }],
78 | });
79 | }
80 | onSubmit = (e) => {
81 | e.preventDefault();
82 | this.props.form.validateFields((error, values) => {
83 | if (!error) {
84 | console.log('ok', values);
85 | } else {
86 | console.log('error', error, values);
87 | }
88 | });
89 | };
90 | changeUseInput = (e) => {
91 | this.setState({
92 | useInput: e.target.checked,
93 | });
94 | }
95 | render() {
96 | const { getFieldError } = this.props.form;
97 | return (
98 |
112 | );
113 | }
114 | }
115 |
116 | class Form3 extends React.Component {
117 | static propTypes = {
118 | form: formShape,
119 | };
120 | state = {
121 | useInput: false,
122 | };
123 | onSubmit = (e) => {
124 | e.preventDefault();
125 | this.props.form.validateFields((error, values) => {
126 | if (!error) {
127 | console.log('ok', values);
128 | } else {
129 | console.log('error', error, values);
130 | }
131 | });
132 | };
133 | changeUseInput = (e) => {
134 | this.setState({
135 | useInput: e.target.checked,
136 | });
137 | }
138 | render() {
139 | const { getFieldError, getFieldDecorator } = this.props.form;
140 | return (
141 |
166 | );
167 | }
168 | }
169 |
170 | const WrappedForm1 = createForm()(Form1);
171 | const WrappedForm2 = createForm()(Form2);
172 | const WrappedForm3 = createForm()(Form3);
173 |
174 | ReactDOM.render(
175 |
176 |
177 |
178 |
179 |
180 | , document.getElementById('__react-content'));
181 |
--------------------------------------------------------------------------------
/examples/dynamic.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/dynamic.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import PropTypes from 'prop-types';
6 | import ReactDOM from 'react-dom';
7 | import { regionStyle, errorStyle } from './styles';
8 |
9 | function Email(props) {
10 | const { hidden, form } = props;
11 | const { getFieldProps, getFieldError, isFieldValidating } = form;
12 | const errors = getFieldError('email');
13 | const style = {
14 | ...regionStyle,
15 | display: hidden ? 'none' : '',
16 | };
17 | return (
18 |
email:
19 |
32 |
33 | {errors ?
{errors.join(',')}
: null}
34 |
35 | {isFieldValidating('email') ?
validating
: null}
36 |
);
37 | }
38 |
39 | Email.propTypes = {
40 | form: formShape,
41 | hidden: PropTypes.bool,
42 | };
43 |
44 | class User extends Component {
45 | static propTypes = {
46 | form: formShape,
47 | };
48 |
49 | render() {
50 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
51 | const errors = getFieldError('user');
52 | return (
53 |
user:
54 |
63 |
64 | {errors ?
{errors.join(',')}
: null}
65 |
66 | {isFieldValidating('user') ?
validating
: null}
67 |
);
68 | }
69 | }
70 |
71 | class Form extends Component {
72 | static propTypes = {
73 | form: formShape,
74 | };
75 |
76 | onSubmit = (e) => {
77 | e.preventDefault();
78 | this.props.form.validateFields((error, values) => {
79 | if (!error) {
80 | console.log('ok', values);
81 | } else {
82 | console.log('error', error, values);
83 | }
84 | });
85 | };
86 |
87 | render() {
88 | const { form } = this.props;
89 | const { getFieldProps, getFieldValue } = form;
90 | return ();
129 | }
130 | }
131 |
132 | const NewForm = createForm()(Form);
133 |
134 | ReactDOM.render(, document.getElementById('__react-content'));
135 |
--------------------------------------------------------------------------------
/examples/file-input.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/file-input.html
--------------------------------------------------------------------------------
/examples/file-input.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0,
2 | no-undef:0, react/no-unescaped-entities:0 */
3 |
4 | import { createForm, formShape } from 'rc-form';
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 | import { regionStyle, errorStyle } from './styles';
8 |
9 | function getFileValueProps(value) {
10 | if (value && value.target) {
11 | return {
12 | value: value.target.value,
13 | };
14 | }
15 | return {
16 | value,
17 | };
18 | }
19 |
20 | function getValueFromFileEvent({ target }) {
21 | return {
22 | target,
23 | };
24 | }
25 |
26 | class Form extends React.Component {
27 | static propTypes = {
28 | form: formShape,
29 | };
30 | onSubmit = (e) => {
31 | e.preventDefault();
32 | this.props.form.validateFields((error, values) => {
33 | console.log(error, values);
34 | if (!error) {
35 | const data = new FormData();
36 | data.append('file', values.attachment.target.files[0]);
37 | fetch('/post.htm', {
38 | method: 'post',
39 | body: data,
40 | });
41 | }
42 | });
43 | }
44 | checkSize = (rule, value, callback) => {
45 | if (value && value.target) {
46 | const files = value.target.files;
47 | if (files[0]) {
48 | callback(files[0].size > 1000000 ? 'file size must be less than 1M' : undefined);
49 | } else {
50 | callback();
51 | }
52 | } else {
53 | callback();
54 | }
55 | }
56 | render() {
57 | const { getFieldProps, getFieldError } = this.props.form;
58 | const errors = getFieldError('attachment');
59 | return (
62 |
attachment:
63 |
64 |
70 |
71 |
72 | {(errors) ? errors.join(',') : null}
73 |
74 |
75 |
);
76 | }
77 | }
78 |
79 | Form = createForm()(Form);
80 |
81 | class App extends React.Component {
82 | render() {
83 | return (
84 |
input[type="file"]
85 |
86 | );
87 | }
88 | }
89 |
90 | ReactDOM.render(, document.getElementById('__react-content'));
91 |
--------------------------------------------------------------------------------
/examples/getFieldDecorator.html:
--------------------------------------------------------------------------------
1 | placeholder
2 |
--------------------------------------------------------------------------------
/examples/getFieldDecorator.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { createForm, formShape } from 'rc-form';
6 |
7 | class Form extends React.Component {
8 | static propTypes = {
9 | form: formShape,
10 | };
11 |
12 | componentWillMount() {
13 | this.nameDecorator = this.props.form.getFieldDecorator('name', {
14 | initialValue: '',
15 | rules: [{
16 | required: true,
17 | message: 'What\'s your name?',
18 | }],
19 | });
20 | }
21 |
22 | onSubmit = (e) => {
23 | e.preventDefault();
24 | this.props.form.validateFields((error, values) => {
25 | if (!error) {
26 | console.log('ok', values);
27 | } else {
28 | console.log('error', error, values);
29 | }
30 | });
31 | };
32 |
33 | onChange = (e) => {
34 | console.log(e.target.value);
35 | }
36 |
37 | render() {
38 | const { getFieldError } = this.props.form;
39 |
40 | return (
41 |
52 | );
53 | }
54 | }
55 |
56 | const WrappedForm = createForm()(Form);
57 | ReactDOM.render(, document.getElementById('__react-content'));
58 |
--------------------------------------------------------------------------------
/examples/input-array.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/input-array.html
--------------------------------------------------------------------------------
/examples/input-array.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0, react/jsx-no-bind:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { regionStyle } from './styles';
7 |
8 | let uuid = 0;
9 |
10 |
11 | class Form extends React.Component {
12 | static propTypes = {
13 | form: formShape,
14 | };
15 | remove = (k) => {
16 | const { form } = this.props;
17 | // can use data-binding to get
18 | let keys = form.getFieldValue('keys');
19 | keys = keys.filter((key) => {
20 | return key !== k;
21 | });
22 | // can use data-binding to set
23 | form.setFieldsValue({
24 | keys,
25 | });
26 | }
27 | add = () => {
28 | uuid++;
29 | const { form } = this.props;
30 | // can use data-binding to get
31 | let keys = form.getFieldValue('keys');
32 | keys = keys.concat(uuid);
33 | // can use data-binding to set
34 | // important! notify form to detect changes
35 | form.setFieldsValue({
36 | keys,
37 | });
38 | }
39 | submit = (e) => {
40 | e.preventDefault();
41 | console.log(this.props.form.getFieldsValue());
42 | }
43 | render() {
44 | const { getFieldProps, getFieldValue } = this.props.form;
45 | getFieldProps('keys', {
46 | initialValue: [],
47 | });
48 | const inputs = getFieldValue('keys').map((k) => {
49 | return ();
54 | });
55 | return (
56 | {inputs}
57 |
58 |
59 |
60 |
61 |
62 |
);
63 | }
64 | }
65 |
66 | Form = createForm()(Form);
67 |
68 | ReactDOM.render(, document.getElementById('__react-content'));
69 |
--------------------------------------------------------------------------------
/examples/modal.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/modal.html
--------------------------------------------------------------------------------
/examples/modal.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/no-string-refs:0 */
2 |
3 | import createDOMForm from 'rc-form/src/createDOMForm';
4 | import { formShape } from 'rc-form';
5 | import React, { Component } from 'react';
6 | import ReactDOM from 'react-dom';
7 | import Modal from 'antd/lib/modal';
8 | import 'antd/dist/antd.css';
9 | import { regionStyle, errorStyle } from './styles';
10 |
11 | class Form extends Component {
12 | static propTypes = {
13 | form: formShape,
14 | };
15 |
16 | constructor() {
17 | super();
18 | this.state = {
19 | visible: false,
20 | };
21 | }
22 |
23 | onSubmit = (e) => {
24 | e.preventDefault();
25 | this.props.form.validateFieldsAndScroll((error, values) => {
26 | if (!error) {
27 | console.log('ok', values);
28 | } else {
29 | console.log('error', error, values);
30 | }
31 | });
32 | };
33 |
34 | onCancel = () => {
35 | this.setState({
36 | visible: false,
37 | });
38 | };
39 |
40 | open = () => {
41 | this.setState({
42 | visible: true,
43 | });
44 | };
45 |
46 | render() {
47 | const { getFieldProps, getFieldError } = this.props.form;
48 | return (
49 |
modal
50 |
59 |
78 |
79 |
80 |
81 |
82 |
);
83 | }
84 | }
85 |
86 | const NewForm = createDOMForm()(Form);
87 |
88 | ReactDOM.render(, document.getElementById('__react-content'));
89 |
--------------------------------------------------------------------------------
/examples/nested-field.html:
--------------------------------------------------------------------------------
1 | placeholder
2 |
--------------------------------------------------------------------------------
/examples/nested-field.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { formShape } from 'rc-form';
6 | import createForm from '../src/createDOMForm';
7 |
8 | class Form extends React.Component {
9 | static propTypes = {
10 | form: formShape,
11 | };
12 |
13 | onSubmit = (e) => {
14 | e.preventDefault();
15 | console.log('Values of member[0].name.firstname and a[0][1].b.c[0]');
16 | console.log(this.props.form.getFieldsValue(['member[0].name.firstname', 'a[0][1].b.c[0]']));
17 | console.log('Values of all fields');
18 | console.log(this.props.form.getFieldsValue());
19 |
20 | this.props.form.validateFieldsAndScroll((error, values) => {
21 | if (!error) {
22 | console.log('ok', values);
23 | } else {
24 | console.log('error', error, values);
25 | }
26 | });
27 | }
28 |
29 | onChange = (e) => {
30 | console.log(e.target.value);
31 | }
32 |
33 | setField = () => {
34 | this.props.form.setFieldsValue({
35 | member: [
36 | {
37 | name: {
38 | firstname: 'm1 first',
39 | lastname: 'm1 last',
40 | },
41 | },
42 | {
43 | name: {
44 | firstname: 'm2 first',
45 | lastname: 'm2 last',
46 | },
47 | },
48 | ],
49 | a: [
50 | [undefined, {
51 | b: {
52 | c: ['Value of a[0][1].b.c[0]'],
53 | },
54 | }],
55 | ],
56 | w: {
57 | x: {
58 | y: {
59 | z: ['Value of w.x.y.z[0]'],
60 | },
61 | },
62 | },
63 | });
64 | }
65 |
66 | resetFields = () => {
67 | console.log('reset');
68 | this.props.form.resetFields();
69 | }
70 |
71 | render() {
72 | const { getFieldDecorator, getFieldError } = this.props.form;
73 |
74 | return (
75 |
176 | );
177 | }
178 | }
179 |
180 | Form = createForm({
181 | onFieldsChange(_, changedFields, allFields) {
182 | console.log('onFieldsChange: ', changedFields, allFields);
183 | },
184 | onValuesChange(_, changedValues, allValues) {
185 | console.log('onValuesChange: ', changedValues, allValues);
186 | },
187 | })(Form);
188 |
189 | class App extends React.Component {
190 | render() {
191 | return (
192 |
setFieldsValue
193 |
194 | );
195 | }
196 | }
197 |
198 | ReactDOM.render(, document.getElementById('__react-content'));
199 |
--------------------------------------------------------------------------------
/examples/normalize.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/normalize.html
--------------------------------------------------------------------------------
/examples/normalize.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import 'antd/dist/antd.css';
7 | import { regionStyle, errorStyle } from './styles';
8 |
9 | class CustomInput extends React.Component {
10 | static propTypes = {
11 | form: formShape,
12 | };
13 | state = {};
14 | checkUpper = (rule, value, callback) => {
15 | if (value !== value.toUpperCase()) {
16 | callback(new Error('need to be upper!'));
17 | } else {
18 | callback();
19 | }
20 | }
21 | toUpper = (v, prev) => {
22 | if (v === prev) {
23 | return v;
24 | }
25 | return v.toUpperCase();
26 | }
27 | render() {
28 | const { getFieldProps, getFieldError } = this.props.form;
29 | const errors = getFieldError('upper');
30 | return (
31 |
upper normalize
32 |
33 |
40 |
41 |
42 | {(errors) ? errors.join(',') : null}
43 |
44 |
);
45 | }
46 | }
47 |
48 | class MaxMin extends React.Component {
49 | static propTypes = {
50 | form: formShape,
51 | };
52 | normalizeMin = (value, prevValue, allValues) => {
53 | console.log('normalizeMin', allValues.min, allValues.max);
54 | const previousAllValues = this.props.form.getFieldsValue();
55 | if (allValues.max !== previousAllValues.max) {
56 | // max changed
57 | if (value === '' || Number(allValues.max) < Number(value)) {
58 | return allValues.max;
59 | }
60 | }
61 | return value;
62 | }
63 | normalizeMax = (value, prevValue, allValues) => {
64 | console.log('normalizeMax', allValues.min, allValues.max);
65 | const previousAllValues = this.props.form.getFieldsValue();
66 | if (allValues.min !== previousAllValues.min) {
67 | // min changed
68 | if (value === '' || Number(allValues.min) > Number(value)) {
69 | return allValues.min;
70 | }
71 | }
72 | return value;
73 | }
74 | render() {
75 | const { getFieldProps } = this.props.form;
76 | return (
77 |
min:
90 |
91 |
max:
104 |
105 |
);
106 | }
107 | }
108 |
109 | class Form extends Component {
110 | static propTypes = {
111 | form: formShape,
112 | }
113 |
114 | onSubmit = (e) => {
115 | e.preventDefault();
116 | this.props.form.validateFields((error, values) => {
117 | if (!error) {
118 | console.log('ok', values);
119 | } else {
120 | console.log('error', error, values);
121 | }
122 | });
123 | }
124 |
125 | render() {
126 | const { form } = this.props;
127 | return (
128 |
normalize
129 |
138 |
);
139 | }
140 | }
141 |
142 | const NewForm = createForm()(Form);
143 |
144 | ReactDOM.render(, document.getElementById('__react-content'));
145 |
--------------------------------------------------------------------------------
/examples/overview.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/overview.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import createDOMForm from 'rc-form/src/createDOMForm';
4 | import { formShape } from 'rc-form';
5 | import React, { Component } from 'react';
6 | import ReactDOM from 'react-dom';
7 | import Select, { Option } from 'antd/lib/select';
8 | import DatePicker from 'antd/lib/date-picker';
9 | import 'antd/dist/antd.css';
10 | import { regionStyle, errorStyle } from './styles';
11 |
12 | function Email(props) {
13 | const { getFieldProps, getFieldError, isFieldValidating } = props.form;
14 | const errors = getFieldError('email');
15 | return (
16 |
email sync validate
17 |
18 | 错误的 email 格式,
24 | },
25 | ],
26 | })}
27 | />
28 |
29 | {errors}
30 |
31 |
32 | {isFieldValidating('email') ? 'validating' : null}
33 |
34 |
);
35 | }
36 |
37 | Email.propTypes = {
38 | form: formShape,
39 | };
40 |
41 | class User extends React.Component {
42 | static propTypes = {
43 | form: formShape,
44 | };
45 | userExists = (rule, value, callback) => {
46 | setTimeout(() => {
47 | if (value === '1') {
48 | callback([new Error('are you kidding?')]);
49 | } else if (value === 'yiminghe') {
50 | callback([new Error('forbid yiminghe')]);
51 | } else {
52 | callback();
53 | }
54 | }, 300);
55 | }
56 | render() {
57 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
58 | const errors = getFieldError('user');
59 | return (
60 |
* user async validate
61 |
62 |
75 |
76 | {(errors) ? errors.join(',') : null}
77 |
78 |
79 | {isFieldValidating('user') ? 'validating' : null}
80 |
81 |
);
82 | }
83 | }
84 |
85 | function CustomInput(props) {
86 | const { getFieldProps, getFieldError, isFieldValidating } = props.form;
87 | const errors = getFieldError('select');
88 | return (
89 |
* custom select sync validate
90 |
104 |
105 | {(errors) ? errors.join(',') : null}
106 |
107 |
108 | {isFieldValidating('select') ? 'validating' : null}
109 |
110 |
);
111 | }
112 |
113 | CustomInput.propTypes = {
114 | form: formShape,
115 | };
116 |
117 | function DateInput(props) {
118 | const { getFieldProps, getFieldError } = props.form;
119 | const errors = getFieldError('date');
120 | return (
121 |
* DateInput sync validate
122 |
123 |
134 |
135 |
136 | {(errors) ? errors.join(',') : null}
137 |
138 |
);
139 | }
140 |
141 | DateInput.propTypes = {
142 | form: formShape,
143 | };
144 |
145 | function toNumber(v) {
146 | if (v === undefined) {
147 | return v;
148 | }
149 | if (v === '') {
150 | return undefined;
151 | }
152 | if (v && v.trim() === '') {
153 | return NaN;
154 | }
155 | return Number(v);
156 | }
157 |
158 | function NumberInput(props) {
159 | const { getFieldProps, getFieldError } = props.form;
160 | const errors = getFieldError('number');
161 | return (
162 |
number input
163 |
164 |
173 |
174 |
175 | {(errors) ? errors.join(',') : null}
176 |
177 |
);
178 | }
179 |
180 | NumberInput.propTypes = {
181 | form: formShape,
182 | };
183 |
184 | class Form extends Component {
185 | static propTypes = {
186 | form: formShape,
187 | };
188 |
189 | onSubmit = (e) => {
190 | console.log('submit');
191 | e.preventDefault();
192 | this.props.form.validateFieldsAndScroll({ scroll: { offsetTop: 20 } }, (error, values) => {
193 | if (!error) {
194 | console.log('ok', values);
195 | } else {
196 | console.log('error', error, values);
197 | }
198 | });
199 | };
200 |
201 | reset = (e) => {
202 | e.preventDefault();
203 | this.props.form.resetFields();
204 | };
205 |
206 | render() {
207 | const { form } = this.props;
208 | const { getFieldProps, getFieldError } = form;
209 | return (
210 |
overview
211 |
245 |
);
246 | }
247 | }
248 |
249 | const NewForm = createDOMForm({
250 | validateMessages: {
251 | required(field) {
252 | return `${field} 必填`;
253 | },
254 | },
255 | })(Form);
256 |
257 | ReactDOM.render(, document.getElementById('__react-content'));
258 |
--------------------------------------------------------------------------------
/examples/parallel-form.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/parallel-form.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React from 'react';
5 | import PropTypes from 'prop-types';
6 | import ReactDOM from 'react-dom';
7 | import Switch from 'antd/lib/switch';
8 | import 'antd/dist/antd.css';
9 | import { regionStyle } from './styles';
10 |
11 | class TopForm extends React.Component {
12 | static propTypes = {
13 | form: formShape,
14 | };
15 | render() {
16 | const { getFieldProps } = this.props.form;
17 | return (
18 |
has email?
19 |
20 |
25 |
26 |
);
27 | }
28 | }
29 |
30 | class BottomForm extends React.Component {
31 | static propTypes = {
32 | form: formShape,
33 | on: PropTypes.bool,
34 | };
35 | render() {
36 | const { form } = this.props;
37 | const on = form.getFieldValue('on');
38 | const style = {
39 | ...regionStyle,
40 | display: on ? 'block' : 'none',
41 | };
42 | return (
43 |
email:
44 |
45 |
52 |
53 |
);
54 | }
55 | }
56 |
57 | class Form extends React.Component {
58 | static propTypes = {
59 | form: formShape,
60 | };
61 | onSubmit = (e) => {
62 | e.preventDefault();
63 | console.log(this.props.form.getFieldsValue());
64 | }
65 | render() {
66 | const { form } = this.props;
67 | return (
68 |
69 |
70 |
71 |
72 |
73 |
);
74 | }
75 | }
76 |
77 | Form = createForm()(Form);
78 |
79 | class App extends React.Component {
80 | render() {
81 | return (
82 |
parallel form
83 |
84 | );
85 | }
86 | }
87 |
88 | ReactDOM.render(, document.getElementById('__react-content'));
89 |
--------------------------------------------------------------------------------
/examples/promise-validate.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/promise-validate.html
--------------------------------------------------------------------------------
/examples/promise-validate.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 |
7 | class Form extends React.Component {
8 | static propTypes = {
9 | form: formShape,
10 | };
11 |
12 | handleSubmit = (e) => {
13 | e.preventDefault();
14 | const { validateFields } = this.props.form;
15 | validateFields()
16 | .then(console.log)
17 | .catch(console.error);
18 | }
19 |
20 | render() {
21 | const { getFieldDecorator } = this.props.form;
22 | return (
23 |
31 | );
32 | }
33 | }
34 |
35 | const NewForm = createForm()(Form);
36 |
37 | ReactDOM.render(, document.getElementById('__react-content'));
38 |
--------------------------------------------------------------------------------
/examples/react-native/App.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/no-multi-comp:0 */
2 |
3 | import React from 'react';
4 | import PropTypes from 'prop-types';
5 | import {
6 | StyleSheet,
7 | Button,
8 | Dimensions,
9 | TextInput,
10 | Text,
11 | View,
12 | Alert,
13 | } from 'react-native';
14 |
15 | import { createForm } from 'rc-form';
16 |
17 | const { width } = Dimensions.get('window');
18 | const styles = StyleSheet.create({
19 | container: {
20 | flex: 1,
21 | backgroundColor: '#fff',
22 | alignItems: 'center',
23 | padding: 50,
24 | justifyContent: 'center',
25 | },
26 | inputView: {
27 | width: width - 100,
28 | paddingLeft: 10,
29 | },
30 | input: {
31 | height: 42,
32 | fontSize: 16,
33 | },
34 | errorinfo: {
35 | marginTop: 10,
36 | },
37 | errorinfoText: {
38 | color: 'red',
39 | },
40 | });
41 |
42 | class FromItem extends React.PureComponent {
43 | static propTypes = {
44 | label: PropTypes.string,
45 | onChange: PropTypes.func,
46 | value: PropTypes.string,
47 | error: PropTypes.array,
48 | };
49 | getError = error => {
50 | if (error) {
51 | return error.map(info => {
52 | return (
53 |
54 | {info}
55 |
56 | );
57 | });
58 | }
59 | return null;
60 | };
61 | render() {
62 | const { label, onChange, value, error } = this.props;
63 | return (
64 |
65 |
74 | {this.getError(error)}
75 |
76 | );
77 | }
78 | }
79 |
80 | class App extends React.Component {
81 | static propTypes = {
82 | form: PropTypes.object.isRequired,
83 | };
84 |
85 | checkUserNameOne = (value, callback) => {
86 | setTimeout(() => {
87 | if (value === '15188888888') {
88 | callback('手机号已经被注册');
89 | } else {
90 | callback();
91 | }
92 | }, 2000);
93 | };
94 | submit = () => {
95 | this.props.form.validateFields((error) => {
96 | if (error) return;
97 | Alert('通过了所有验证'); // eslint-disable-line new-cap
98 | });
99 | };
100 | render() {
101 | const { getFieldDecorator, getFieldError } = this.props.form;
102 | return (
103 |
104 | 简单的手机号验证
105 | {getFieldDecorator('username', {
106 | validateFirst: true,
107 | rules: [
108 | { required: true, message: '请输入手机号!' },
109 | {
110 | pattern: /^1\d{10}$/,
111 | message: '请输入正确的手机号!',
112 | },
113 | {
114 | validator: (rule, value, callback) => {
115 | this.checkUserNameOne(value, callback);
116 | },
117 | message: '手机号已经被注册!',
118 | },
119 | ],
120 | })(
121 |
126 | )}
127 |
128 |
129 | );
130 | }
131 | }
132 |
133 | export default createForm()(App);
134 |
--------------------------------------------------------------------------------
/examples/react-native/expo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/react-native/expo.jpg
--------------------------------------------------------------------------------
/examples/redux.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/redux.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import { createForm, createFormField, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import PropTypes from 'prop-types';
6 | import { combineReducers, createStore } from 'redux';
7 | import { Provider, connect } from 'react-redux';
8 | import ReactDOM from 'react-dom';
9 | import { regionStyle, errorStyle } from './styles';
10 |
11 | function form(state = {
12 | email: {
13 | value: 'x@gmail.com',
14 | },
15 | }, action) {
16 | switch (action.type) {
17 | case 'save_fields':
18 | return {
19 | ...state,
20 | ...action.payload,
21 | };
22 | default:
23 | return state;
24 | }
25 | }
26 |
27 | class Form extends Component {
28 | static propTypes = {
29 | form: formShape,
30 | }
31 |
32 | render() {
33 | const { getFieldProps, getFieldError } = this.props.form;
34 | const errors = getFieldError('email');
35 | return (
36 |
email:
37 |
38 |
44 |
45 | {(errors) ? errors.join(',') : null}
46 |
47 |
);
48 | }
49 | }
50 |
51 | class Out extends React.Component {
52 | static propTypes = {
53 | email: PropTypes.object,
54 | dispatch: PropTypes.func,
55 | };
56 | setEmail = () => {
57 | this.props.dispatch({
58 | type: 'save_fields',
59 | payload: {
60 | email: {
61 | value: 'yiminghe@gmail.com',
62 | },
63 | },
64 | });
65 | }
66 | render() {
67 | const { email } = this.props;
68 | return (
69 |
70 | email: {email && email.value}
71 |
72 |
73 |
);
74 | }
75 | }
76 |
77 | Out = connect((state) => {
78 | return {
79 | email: state.form.email,
80 | };
81 | })(Out);
82 |
83 | const store = createStore(combineReducers({
84 | form,
85 | }));
86 |
87 | const NewForm = connect((state) => {
88 | return {
89 | formState: {
90 | email: state.form.email,
91 | },
92 | };
93 | })(createForm({
94 | mapPropsToFields(props) {
95 | console.log('mapPropsToFields', props);
96 | return {
97 | email: createFormField(props.formState.email),
98 | };
99 | },
100 | onFieldsChange(props, fields) {
101 | console.log('onFieldsChange', fields);
102 | props.dispatch({
103 | type: 'save_fields',
104 | payload: fields,
105 | });
106 | },
107 | })(Form));
108 |
109 | class App extends React.Component {
110 | render() {
111 | return (
112 |
113 |
integrate with redux
114 |
115 |
116 |
117 | );
118 | }
119 | }
120 |
121 | ReactDOM.render(, document.getElementById('__react-content'));
122 |
--------------------------------------------------------------------------------
/examples/server-validate.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/server-validate.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { regionStyle, errorStyle } from './styles';
7 |
8 | function Email(props) {
9 | const { getFieldProps, getFieldError } = props.form;
10 | const errors = getFieldError('email');
11 | return (
12 |
email sync validate
13 |
14 |
26 |
27 | {errors ? errors.join(',') : null}
28 |
29 |
);
30 | }
31 |
32 | Email.propTypes = {
33 | form: formShape,
34 | };
35 |
36 | class User extends React.Component {
37 | static propTypes = {
38 | form: formShape,
39 | };
40 | render() {
41 | const { getFieldProps, getFieldError } = this.props.form;
42 | const errors = getFieldError('user');
43 | return (
44 |
user async validate
45 |
46 |
52 |
53 |
54 | {(errors) ? errors.join(',') : null}
55 |
56 |
);
57 | }
58 | }
59 |
60 | class Form extends Component {
61 | static propTypes = {
62 | form: formShape,
63 | };
64 |
65 | onSubmit = (e) => {
66 | e.preventDefault();
67 | this.props.form.validateFields((error, values) => {
68 | if (!error) {
69 | console.log('ok', values);
70 | setTimeout(() => {
71 | // server validate
72 | if (values.user === 'yiminghe') {
73 | this.props.form.setFields({
74 | user: {
75 | value: values.user,
76 | errors: [new Error('forbid ha')],
77 | },
78 | });
79 | }
80 | }, 500);
81 | } else {
82 | console.log('error', error, values);
83 | }
84 | });
85 | };
86 |
87 | render() {
88 | const { form } = this.props;
89 | return (
90 |
setFields
91 |
100 |
);
101 | }
102 | }
103 |
104 | const NewForm = createForm()(Form);
105 |
106 | ReactDOM.render(, document.getElementById('__react-content'));
107 |
--------------------------------------------------------------------------------
/examples/setFieldsValue.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/setFieldsValue.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { regionStyle, errorStyle } from './styles';
7 |
8 | class Form extends React.Component {
9 | static propTypes = {
10 | form: formShape,
11 | };
12 |
13 | componentDidUpdate() {
14 | console.log('didUpdate');
15 | }
16 |
17 | setEmail = () => {
18 | this.props.form.setFieldsValue({
19 | email: 'yiminghe@gmail.com',
20 | }, () => console.log('after'));
21 | console.log('before');
22 | }
23 |
24 | render() {
25 | const { getFieldProps, getFieldError } = this.props.form;
26 | const errors = getFieldError('email');
27 | return (
28 |
email:
29 |
30 |
36 |
37 |
38 | {(errors) ? errors.join(',') : null}
39 |
40 |
41 |
42 |
);
43 | }
44 | }
45 |
46 | Form = createForm()(Form);
47 |
48 | class App extends React.Component {
49 | render() {
50 | return (
51 |
setFieldsValue
52 |
53 | );
54 | }
55 | }
56 |
57 | ReactDOM.render(, document.getElementById('__react-content'));
58 |
--------------------------------------------------------------------------------
/examples/start-end-date.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/start-end-date.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console:0 */
2 |
3 | import React, { Component } from 'react';
4 | import ReactDOM from 'react-dom';
5 | import DatePicker from 'antd/lib/date-picker';
6 | import { formShape } from 'rc-form';
7 | import 'antd/dist/antd.css';
8 | import createDOMForm from 'rc-form/src/createDOMForm';
9 | import { regionStyle, errorStyle } from './styles';
10 |
11 | class Form extends Component {
12 | static propTypes = {
13 | form: formShape,
14 | };
15 |
16 | onSubmit = (e) => {
17 | console.log('submit');
18 | e.preventDefault();
19 | this.props.form.validateFieldsAndScroll((error, values) => {
20 | if (!error) {
21 | console.log('ok', values);
22 | } else {
23 | console.log('error', error, values);
24 | }
25 | });
26 | };
27 |
28 | reset = (e) => {
29 | e.preventDefault();
30 | this.props.form.resetFields();
31 | };
32 |
33 | checkStart = (rule, value, callback) => {
34 | const { validateFields } = this.props.form;
35 | validateFields(['end'], {
36 | force: true,
37 | });
38 | callback();
39 | };
40 |
41 | checkEnd = (rule, value, callback) => {
42 | const end = value;
43 | const { getFieldValue } = this.props.form;
44 | const start = getFieldValue('start');
45 | if (!end || !start) {
46 | callback('please select both start and end time');
47 | } else if (end.valueOf() < start.valueOf()) {
48 | callback('start time should be less than end time');
49 | } else {
50 | callback();
51 | }
52 | };
53 |
54 | render() {
55 | const { form } = this.props;
56 | const { getFieldProps, getFieldError } = form;
57 | return (
58 |
startTime and endTime validation
59 |
90 |
);
91 | }
92 | }
93 |
94 | const NewForm = createDOMForm()(Form);
95 |
96 | ReactDOM.render(, document.getElementById('__react-content'));
97 |
--------------------------------------------------------------------------------
/examples/styles.js:
--------------------------------------------------------------------------------
1 | export const regionStyle = {
2 | border: '1px solid red',
3 | marginTop: 10,
4 | padding: 10,
5 | };
6 |
7 | export const errorStyle = {
8 | color: 'red',
9 | marginTop: 10,
10 | padding: 10,
11 | };
12 |
--------------------------------------------------------------------------------
/examples/suggest.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-component/form/f02b478c59c178ea9ee28cca3326ad1c4ed37f02/examples/suggest.html
--------------------------------------------------------------------------------
/examples/suggest.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import Select, { Option } from 'antd/lib/select';
7 |
8 | import 'antd/dist/antd.css';
9 | import { regionStyle, errorStyle } from './styles';
10 |
11 | const emailTpl = ['@gmail.com', '@outlook.com', '@qq.com'];
12 |
13 | class CustomInput extends React.Component {
14 | static propTypes = {
15 | form: formShape,
16 | };
17 | state = {
18 | data: [],
19 | };
20 | onChange = (v) => {
21 | if (v.indexOf('@') === -1) {
22 | this.setState({
23 | data: emailTpl.map(m => v + m),
24 | });
25 | } else if (this.state.data.length) {
26 | this.setState({
27 | data: [],
28 | });
29 | }
30 | }
31 | render() {
32 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
33 | const errors = getFieldError('select');
34 | return (
35 |
custom select sync validate
36 |
37 |
60 |
61 |
62 | {(errors) ? errors.join(',') :
63 |
68 | 1
69 | }
70 |
71 |
72 | {isFieldValidating('select') ? 'validating' :
77 | 1
78 | }
79 |
80 |
);
81 | }
82 | }
83 |
84 | class Form extends Component {
85 | static propTypes = {
86 | form: formShape,
87 | };
88 |
89 | onSubmit = (e) => {
90 | e.preventDefault();
91 | this.props.form.validateFields((error, values) => {
92 | if (!error) {
93 | console.log('ok', values);
94 | } else {
95 | console.log('error', error, values);
96 | }
97 | });
98 | };
99 |
100 | render() {
101 | const { form } = this.props;
102 | return (
103 |
suggest
104 |
111 |
);
112 | }
113 | }
114 |
115 | const NewForm = createForm()(Form);
116 |
117 | ReactDOM.render(, document.getElementById('__react-content'));
118 |
--------------------------------------------------------------------------------
/examples/validateFirst.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/validateFirst.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import 'antd/dist/antd.css';
7 | import { regionStyle, errorStyle } from './styles';
8 |
9 | function Email(props) {
10 | const { getFieldProps, getFieldError, isFieldValidating } = props.form;
11 | const errors = getFieldError('email');
12 | return (
13 |
email sync validate
14 |
15 |
29 |
30 |
31 | {errors ? errors.join(',') : null}
32 |
33 |
34 | {isFieldValidating('email') ? 'validating' : null}
35 |
36 |
);
37 | }
38 |
39 | Email.propTypes = {
40 | form: formShape,
41 | };
42 |
43 | class User extends React.Component {
44 | static propTypes = {
45 | form: formShape,
46 | };
47 | userExists = (rule, value, callback) => {
48 | setTimeout(() => {
49 | if (value === '1') {
50 | callback([new Error('are you kidding?')]);
51 | } else if (value === 'yiminghe') {
52 | callback([new Error('forbid yiminghe')]);
53 | } else {
54 | callback();
55 | }
56 | }, 300);
57 | }
58 | render() {
59 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
60 | const errors = getFieldError('user');
61 | return (
62 |
user async validate
63 |
64 |
77 |
78 |
79 | {(errors) ? errors.join(',') : null}
80 |
81 |
82 | {isFieldValidating('user') ? 'validating' : null}
83 |
84 |
);
85 | }
86 | }
87 |
88 | class Form extends Component {
89 | static propTypes = {
90 | form: formShape,
91 | };
92 |
93 | onSubmit = (e) => {
94 | console.log('submit');
95 | e.preventDefault();
96 | this.props.form.validateFields({
97 | // firstFields: false,
98 | }, (error, values) => {
99 | if (!error) {
100 | console.log('ok', values);
101 | } else {
102 | console.log('error', error, values);
103 | }
104 | });
105 | };
106 |
107 | reset = (e) => {
108 | e.preventDefault();
109 | this.props.form.resetFields();
110 | };
111 |
112 | render() {
113 | const { form } = this.props;
114 | return (
115 |
validateFirst
116 |
127 |
);
128 | }
129 | }
130 |
131 | const NewForm = createForm()(Form);
132 |
133 | ReactDOM.render(, document.getElementById('__react-content'));
134 |
--------------------------------------------------------------------------------
/examples/validateTrigger.html:
--------------------------------------------------------------------------------
1 | placeholder
--------------------------------------------------------------------------------
/examples/validateTrigger.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp:0, no-console:0, react/prefer-stateless-function: 0 */
2 |
3 | import { createForm, formShape } from 'rc-form';
4 | import React, { Component } from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { regionStyle, errorStyle } from './styles';
7 |
8 | function Email(props) {
9 | const { getFieldProps, getFieldError, isFieldValidating } = props.form;
10 | const errors = getFieldError('email');
11 | return (
12 |
email validate onBlur && onChange
13 |
14 |
29 |
30 |
31 | {errors ? errors.join(',') : null}
32 |
33 |
34 | {isFieldValidating('email') ? 'validating' : null}
35 |
36 |
);
37 | }
38 |
39 | Email.propTypes = {
40 | form: formShape,
41 | };
42 |
43 | class User extends React.Component {
44 | static propTypes = {
45 | form: formShape,
46 | };
47 | render() {
48 | const { getFieldProps, getFieldError, isFieldValidating } = this.props.form;
49 | const errors = getFieldError('user');
50 | return (
51 |
user validate on submit
52 |
53 |
66 |
67 |
68 | {(errors) ? errors.join(',') : null}
69 |
70 |
71 | {isFieldValidating('user') ? 'validating' : null}
72 |
73 |
);
74 | }
75 | }
76 |
77 | class Form extends Component {
78 | static propTypes = {
79 | form: formShape,
80 | };
81 |
82 | onSubmit = (e) => {
83 | e.preventDefault();
84 | this.props.form.validateFields((error, values) => {
85 | if (!error) {
86 | console.log('ok', values);
87 | } else {
88 | console.log('error', error, values);
89 | }
90 | });
91 | };
92 |
93 | render() {
94 | const { form } = this.props;
95 | return (
96 |
use validateTrigger config
97 |
106 |
);
107 | }
108 | }
109 |
110 | const NewForm = createForm()(Form);
111 |
112 | ReactDOM.render(, document.getElementById('__react-content'));
113 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import { createForm, createFormField } from './src/';
3 | export { createForm, createFormField };
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rc-form",
3 | "version": "2.4.12",
4 | "description": "React High Order Form Component",
5 | "keywords": [
6 | "react",
7 | "react-component",
8 | "react-form",
9 | "form"
10 | ],
11 | "homepage": "https://github.com/react-component/form",
12 | "author": "yiminghe@gmail.com",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/react-component/form.git"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/react-component/form/issues"
19 | },
20 | "files": [
21 | "lib",
22 | "es",
23 | "dist",
24 | "assets/*.css"
25 | ],
26 | "license": "MIT",
27 | "main": "./lib/index",
28 | "module": "./es/index",
29 | "config": {
30 | "port": 8000,
31 | "entry": {
32 | "rc-form": [
33 | "./scripts/index.js"
34 | ]
35 | }
36 | },
37 | "scripts": {
38 | "build": "rc-tools run build",
39 | "compile": "rc-tools run compile --babel-runtime",
40 | "gh-pages": "rc-tools run gh-pages",
41 | "start": "rc-tools run server",
42 | "pub": "rc-tools run pub --babel-runtime",
43 | "lint": "rc-tools run lint",
44 | "lint:fix": "rc-tools run lint --fix",
45 | "test": "jest",
46 | "prepublish": "rc-tools run guard",
47 | "coverage": "jest --coverage"
48 | },
49 | "jest": {
50 | "setupFiles": [
51 | "./tests/setup.js"
52 | ],
53 | "collectCoverageFrom": [
54 | "src/**/*"
55 | ],
56 | "snapshotSerializers": [
57 | "enzyme-to-json/serializer"
58 | ],
59 | "transform": {
60 | "\\.jsx?$": "./node_modules/rc-tools/scripts/jestPreprocessor.js"
61 | }
62 | },
63 | "devDependencies": {
64 | "antd": "3.x",
65 | "async": "^3.0.0",
66 | "enzyme": "^3.1.0",
67 | "enzyme-adapter-react-16": "^1.0.2",
68 | "enzyme-to-json": "^3.1.4",
69 | "jest": "^21.2.1",
70 | "pre-commit": "1.x",
71 | "rc-tools": "8.x",
72 | "react": "^16.1.1",
73 | "react-dom": "^16.1.1",
74 | "react-test-renderer": "^16.1.1",
75 | "history": "^1.16.0",
76 | "prop-types": "^15.5.10",
77 | "react-redux": "^4.0.0",
78 | "react-router": "^3.0.0",
79 | "redux": "^3.0.4"
80 | },
81 | "pre-commit": [
82 | "lint"
83 | ],
84 | "dependencies": {
85 | "async-validator": "~1.11.3",
86 | "babel-runtime": "6.x",
87 | "create-react-class": "^15.5.3",
88 | "dom-scroll-into-view": "1.x",
89 | "hoist-non-react-statics": "^3.3.0",
90 | "lodash": "^4.17.4",
91 | "rc-util": "^4.15.3",
92 | "react-is": "^16.13.1",
93 | "warning": "^4.0.3"
94 | },
95 | "peerDependencies": {
96 | "prop-types": "^15.0"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/scripts/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import createForm from '../src/createForm';
3 | import createDOMForm from '../src/createDOMForm';
4 | if (typeof window !== 'undefined') {
5 | window.RCForm = { createDOMForm, createForm };
6 | }
7 | export { createDOMForm, createForm };
8 |
--------------------------------------------------------------------------------
/src/FieldElemWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class FieldElemWrapper extends React.Component {
5 | componentDidMount() {
6 | const { name, form } = this.props;
7 | form.domFields[name] = true;
8 | form.recoverClearedField(name);
9 | }
10 |
11 | componentWillUnmount() {
12 | const { name, form } = this.props;
13 | const fieldMeta = form.fieldsStore.getFieldMeta(name);
14 | if (!fieldMeta.preserve) {
15 | // after destroy, delete data
16 | form.clearedFieldMetaCache[name] = {
17 | field: form.fieldsStore.getField(name),
18 | meta: fieldMeta,
19 | };
20 | form.clearField(name);
21 | }
22 | delete form.domFields[name];
23 | }
24 |
25 | render() {
26 | return this.props.children;
27 | }
28 | }
29 |
30 | FieldElemWrapper.propTypes = {
31 | name: PropTypes.string,
32 | form: PropTypes.shape({
33 | domFields: PropTypes.objectOf(PropTypes.bool),
34 | recoverClearedField: PropTypes.func,
35 | fieldsStore: PropTypes.shape({
36 | getFieldMeta: PropTypes.func,
37 | getField: PropTypes.func,
38 | }),
39 | clearedFieldMetaCache: PropTypes.objectOf(
40 | PropTypes.shape({
41 | field: PropTypes.object,
42 | meta: PropTypes.object,
43 | }),
44 | ),
45 | clearField: PropTypes.func,
46 | }),
47 | children: PropTypes.node,
48 | };
49 |
--------------------------------------------------------------------------------
/src/createBaseForm.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prefer-es6-class */
2 | /* eslint-disable prefer-promise-reject-errors */
3 |
4 | import React from 'react';
5 | import createReactClass from 'create-react-class';
6 | import unsafeLifecyclesPolyfill from 'rc-util/lib/unsafeLifecyclesPolyfill';
7 | import AsyncValidator from 'async-validator';
8 | import warning from 'warning';
9 | import get from 'lodash/get';
10 | import set from 'lodash/set';
11 | import eq from 'lodash/eq';
12 | import createFieldsStore from './createFieldsStore';
13 | import {
14 | argumentContainer,
15 | identity,
16 | normalizeValidateRules,
17 | getValidateTriggers,
18 | getValueFromEvent,
19 | hasRules,
20 | getParams,
21 | isEmptyObject,
22 | flattenArray,
23 | supportRef
24 | } from './utils';
25 | import FieldElemWrapper from './FieldElemWrapper';
26 |
27 | const DEFAULT_TRIGGER = 'onChange';
28 |
29 | function createBaseForm(option = {}, mixins = []) {
30 | const {
31 | validateMessages,
32 | onFieldsChange,
33 | onValuesChange,
34 | mapProps = identity,
35 | mapPropsToFields,
36 | fieldNameProp,
37 | fieldMetaProp,
38 | fieldDataProp,
39 | formPropName = 'form',
40 | name: formName,
41 | // @deprecated
42 | withRef,
43 | } = option;
44 |
45 | return function decorate(WrappedComponent) {
46 | const Form = createReactClass({
47 | mixins,
48 |
49 | getInitialState() {
50 | const fields = mapPropsToFields && mapPropsToFields(this.props);
51 | this.fieldsStore = createFieldsStore(fields || {});
52 |
53 | this.instances = {};
54 | this.cachedBind = {};
55 | this.clearedFieldMetaCache = {};
56 |
57 | this.renderFields = {};
58 | this.domFields = {};
59 |
60 | // HACK: https://github.com/ant-design/ant-design/issues/6406
61 | [
62 | 'getFieldsValue',
63 | 'getFieldValue',
64 | 'setFieldsInitialValue',
65 | 'getFieldsError',
66 | 'getFieldError',
67 | 'isFieldValidating',
68 | 'isFieldsValidating',
69 | 'isFieldsTouched',
70 | 'isFieldTouched',
71 | ].forEach(key => {
72 | this[key] = (...args) => {
73 | if (process.env.NODE_ENV !== 'production') {
74 | warning(
75 | false,
76 | 'you should not use `ref` on enhanced form, please use `wrappedComponentRef`. ' +
77 | 'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140',
78 | );
79 | }
80 | return this.fieldsStore[key](...args);
81 | };
82 | });
83 |
84 | return {
85 | submitting: false,
86 | };
87 | },
88 |
89 | componentDidMount() {
90 | this.cleanUpUselessFields();
91 | },
92 |
93 | componentWillReceiveProps(nextProps) {
94 | if (mapPropsToFields) {
95 | this.fieldsStore.updateFields(mapPropsToFields(nextProps));
96 | }
97 | },
98 |
99 | componentDidUpdate() {
100 | this.cleanUpUselessFields();
101 | },
102 |
103 | onCollectCommon(name, action, args) {
104 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
105 | if (fieldMeta[action]) {
106 | fieldMeta[action](...args);
107 | } else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
108 | fieldMeta.originalProps[action](...args);
109 | }
110 | const value = fieldMeta.getValueFromEvent
111 | ? fieldMeta.getValueFromEvent(...args)
112 | : getValueFromEvent(...args);
113 | if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {
114 | const valuesAll = this.fieldsStore.getAllValues();
115 | const valuesAllSet = {};
116 | valuesAll[name] = value;
117 | Object.keys(valuesAll).forEach(key =>
118 | set(valuesAllSet, key, valuesAll[key]),
119 | );
120 | onValuesChange(
121 | {
122 | [formPropName]: this.getForm(),
123 | ...this.props,
124 | },
125 | set({}, name, value),
126 | valuesAllSet,
127 | );
128 | }
129 | const field = this.fieldsStore.getField(name);
130 | return { name, field: { ...field, value, touched: true }, fieldMeta };
131 | },
132 |
133 | onCollect(name_, action, ...args) {
134 | const { name, field, fieldMeta } = this.onCollectCommon(
135 | name_,
136 | action,
137 | args,
138 | );
139 | const { validate } = fieldMeta;
140 |
141 | this.fieldsStore.setFieldsAsDirty();
142 |
143 | const newField = {
144 | ...field,
145 | dirty: hasRules(validate),
146 | };
147 | this.setFields({
148 | [name]: newField,
149 | });
150 | },
151 |
152 | onCollectValidate(name_, action, ...args) {
153 | const { field, fieldMeta } = this.onCollectCommon(name_, action, args);
154 | const newField = {
155 | ...field,
156 | dirty: true,
157 | };
158 |
159 | this.fieldsStore.setFieldsAsDirty();
160 |
161 | this.validateFieldsInternal([newField], {
162 | action,
163 | options: {
164 | firstFields: !!fieldMeta.validateFirst,
165 | },
166 | });
167 | },
168 |
169 | getCacheBind(name, action, fn) {
170 | if (!this.cachedBind[name]) {
171 | this.cachedBind[name] = {};
172 | }
173 | const cache = this.cachedBind[name];
174 | if (!cache[action] || cache[action].oriFn !== fn) {
175 | cache[action] = {
176 | fn: fn.bind(this, name, action),
177 | oriFn: fn,
178 | };
179 | }
180 | return cache[action].fn;
181 | },
182 |
183 | getFieldDecorator(name, fieldOption) {
184 | const props = this.getFieldProps(name, fieldOption);
185 | return fieldElem => {
186 | // We should put field in record if it is rendered
187 | this.renderFields[name] = true;
188 |
189 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
190 | const originalProps = fieldElem.props;
191 | if (process.env.NODE_ENV !== 'production') {
192 | const valuePropName = fieldMeta.valuePropName;
193 | warning(
194 | !(valuePropName in originalProps),
195 | `\`getFieldDecorator\` will override \`${valuePropName}\`, ` +
196 | `so please don't set \`${valuePropName}\` directly ` +
197 | `and use \`setFieldsValue\` to set it.`,
198 | );
199 | const defaultValuePropName = `default${valuePropName[0].toUpperCase()}${valuePropName.slice(
200 | 1,
201 | )}`;
202 | warning(
203 | !(defaultValuePropName in originalProps),
204 | `\`${defaultValuePropName}\` is invalid ` +
205 | `for \`getFieldDecorator\` will set \`${valuePropName}\`,` +
206 | ` please use \`option.initialValue\` instead.`,
207 | );
208 | }
209 | fieldMeta.originalProps = originalProps;
210 | fieldMeta.ref = fieldElem.ref;
211 | const decoratedFieldElem = React.cloneElement(fieldElem, {
212 | ...props,
213 | ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
214 | });
215 | return supportRef(fieldElem) ? (
216 | decoratedFieldElem
217 | ) : (
218 |
219 | {decoratedFieldElem}
220 |
221 | );
222 | };
223 | },
224 |
225 | getFieldProps(name, usersFieldOption = {}) {
226 | if (!name) {
227 | throw new Error('Must call `getFieldProps` with valid name string!');
228 | }
229 | if (process.env.NODE_ENV !== 'production') {
230 | warning(
231 | this.fieldsStore.isValidNestedFieldName(name),
232 | `One field name cannot be part of another, e.g. \`a\` and \`a.b\`. Check field: ${name}`,
233 | );
234 | warning(
235 | !('exclusive' in usersFieldOption),
236 | '`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.',
237 | );
238 | }
239 |
240 | delete this.clearedFieldMetaCache[name];
241 |
242 | const fieldOption = {
243 | name,
244 | trigger: DEFAULT_TRIGGER,
245 | valuePropName: 'value',
246 | validate: [],
247 | ...usersFieldOption,
248 | };
249 |
250 | const {
251 | rules,
252 | trigger,
253 | validateTrigger = trigger,
254 | validate,
255 | } = fieldOption;
256 |
257 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
258 | if ('initialValue' in fieldOption) {
259 | fieldMeta.initialValue = fieldOption.initialValue;
260 | }
261 |
262 | const inputProps = {
263 | ...this.fieldsStore.getFieldValuePropValue(fieldOption),
264 | ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
265 | };
266 | if (fieldNameProp) {
267 | inputProps[fieldNameProp] = formName ? `${formName}_${name}` : name;
268 | }
269 |
270 | const validateRules = normalizeValidateRules(
271 | validate,
272 | rules,
273 | validateTrigger,
274 | );
275 | const validateTriggers = getValidateTriggers(validateRules);
276 | validateTriggers.forEach(action => {
277 | if (inputProps[action]) return;
278 | inputProps[action] = this.getCacheBind(
279 | name,
280 | action,
281 | this.onCollectValidate,
282 | );
283 | });
284 |
285 | // make sure that the value will be collect
286 | if (trigger && validateTriggers.indexOf(trigger) === -1) {
287 | inputProps[trigger] = this.getCacheBind(
288 | name,
289 | trigger,
290 | this.onCollect,
291 | );
292 | }
293 |
294 | const meta = {
295 | ...fieldMeta,
296 | ...fieldOption,
297 | validate: validateRules,
298 | };
299 | this.fieldsStore.setFieldMeta(name, meta);
300 | if (fieldMetaProp) {
301 | inputProps[fieldMetaProp] = meta;
302 | }
303 |
304 | if (fieldDataProp) {
305 | inputProps[fieldDataProp] = this.fieldsStore.getField(name);
306 | }
307 |
308 | // This field is rendered, record it
309 | this.renderFields[name] = true;
310 |
311 | return inputProps;
312 | },
313 |
314 | getFieldInstance(name) {
315 | return this.instances[name];
316 | },
317 |
318 | getRules(fieldMeta, action) {
319 | const actionRules = fieldMeta.validate
320 | .filter(item => {
321 | return !action || item.trigger.indexOf(action) >= 0;
322 | })
323 | .map(item => item.rules);
324 | return flattenArray(actionRules);
325 | },
326 |
327 | setFields(maybeNestedFields, callback) {
328 | const fields = this.fieldsStore.flattenRegisteredFields(
329 | maybeNestedFields,
330 | );
331 | this.fieldsStore.setFields(fields);
332 | if (onFieldsChange) {
333 | const changedFields = Object.keys(fields).reduce(
334 | (acc, name) => set(acc, name, this.fieldsStore.getField(name)),
335 | {},
336 | );
337 | onFieldsChange(
338 | {
339 | [formPropName]: this.getForm(),
340 | ...this.props,
341 | },
342 | changedFields,
343 | this.fieldsStore.getNestedAllFields(),
344 | );
345 | }
346 | this.forceUpdate(callback);
347 | },
348 |
349 | setFieldsValue(changedValues, callback) {
350 | const { fieldsMeta } = this.fieldsStore;
351 | const values = this.fieldsStore.flattenRegisteredFields(changedValues);
352 | const newFields = Object.keys(values).reduce((acc, name) => {
353 | const isRegistered = fieldsMeta[name];
354 | if (process.env.NODE_ENV !== 'production') {
355 | warning(
356 | isRegistered,
357 | 'Cannot use `setFieldsValue` until ' +
358 | 'you use `getFieldDecorator` or `getFieldProps` to register it.',
359 | );
360 | }
361 | if (isRegistered) {
362 | const value = values[name];
363 | acc[name] = {
364 | value,
365 | };
366 | }
367 | return acc;
368 | }, {});
369 | this.setFields(newFields, callback);
370 | if (onValuesChange) {
371 | const allValues = this.fieldsStore.getAllValues();
372 | onValuesChange(
373 | {
374 | [formPropName]: this.getForm(),
375 | ...this.props,
376 | },
377 | changedValues,
378 | allValues,
379 | );
380 | }
381 | },
382 |
383 | saveRef(name, _, component) {
384 | if (!component) {
385 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
386 | if (!fieldMeta.preserve) {
387 | // after destroy, delete data
388 | this.clearedFieldMetaCache[name] = {
389 | field: this.fieldsStore.getField(name),
390 | meta: fieldMeta,
391 | };
392 | this.clearField(name);
393 | }
394 | delete this.domFields[name];
395 | return;
396 | }
397 | this.domFields[name] = true;
398 | this.recoverClearedField(name);
399 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
400 | if (fieldMeta) {
401 | const ref = fieldMeta.ref;
402 | if (ref) {
403 | if (typeof ref === 'string') {
404 | throw new Error(`can not set ref string for ${name}`);
405 | } else if (typeof ref === 'function') {
406 | ref(component);
407 | } else if (Object.prototype.hasOwnProperty.call(ref, 'current')) {
408 | ref.current = component;
409 | }
410 | }
411 | }
412 | this.instances[name] = component;
413 | },
414 |
415 | cleanUpUselessFields() {
416 | const fieldList = this.fieldsStore.getAllFieldsName();
417 | const removedList = fieldList.filter(field => {
418 | const fieldMeta = this.fieldsStore.getFieldMeta(field);
419 | return (
420 | !this.renderFields[field] &&
421 | !this.domFields[field] &&
422 | !fieldMeta.preserve
423 | );
424 | });
425 | if (removedList.length) {
426 | removedList.forEach(this.clearField);
427 | }
428 | this.renderFields = {};
429 | },
430 |
431 | clearField(name) {
432 | this.fieldsStore.clearField(name);
433 | delete this.instances[name];
434 | delete this.cachedBind[name];
435 | },
436 |
437 | resetFields(ns) {
438 | const newFields = this.fieldsStore.resetFields(ns);
439 | if (Object.keys(newFields).length > 0) {
440 | this.setFields(newFields);
441 | }
442 | if (ns) {
443 | const names = Array.isArray(ns) ? ns : [ns];
444 | names.forEach(name => delete this.clearedFieldMetaCache[name]);
445 | } else {
446 | this.clearedFieldMetaCache = {};
447 | }
448 | },
449 |
450 | recoverClearedField(name) {
451 | if (this.clearedFieldMetaCache[name]) {
452 | this.fieldsStore.setFields({
453 | [name]: this.clearedFieldMetaCache[name].field,
454 | });
455 | this.fieldsStore.setFieldMeta(
456 | name,
457 | this.clearedFieldMetaCache[name].meta,
458 | );
459 | delete this.clearedFieldMetaCache[name];
460 | }
461 | },
462 |
463 | validateFieldsInternal(
464 | fields,
465 | { fieldNames, action, options = {} },
466 | callback,
467 | ) {
468 | const allRules = {};
469 | const allValues = {};
470 | const allFields = {};
471 | const alreadyErrors = {};
472 | fields.forEach(field => {
473 | const name = field.name;
474 | if (options.force !== true && field.dirty === false) {
475 | if (field.errors) {
476 | set(alreadyErrors, name, { errors: field.errors });
477 | }
478 | return;
479 | }
480 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
481 | const newField = {
482 | ...field,
483 | };
484 | newField.errors = undefined;
485 | newField.validating = true;
486 | newField.dirty = true;
487 | allRules[name] = this.getRules(fieldMeta, action);
488 | allValues[name] = newField.value;
489 | allFields[name] = newField;
490 | });
491 | this.setFields(allFields);
492 | // in case normalize
493 | Object.keys(allValues).forEach(f => {
494 | allValues[f] = this.fieldsStore.getFieldValue(f);
495 | });
496 | if (callback && isEmptyObject(allFields)) {
497 | callback(
498 | isEmptyObject(alreadyErrors) ? null : alreadyErrors,
499 | this.fieldsStore.getFieldsValue(fieldNames),
500 | );
501 | return;
502 | }
503 | const validator = new AsyncValidator(allRules);
504 | if (validateMessages) {
505 | validator.messages(validateMessages);
506 | }
507 | validator.validate(allValues, options, errors => {
508 | const errorsGroup = {
509 | ...alreadyErrors,
510 | };
511 | if (errors && errors.length) {
512 | errors.forEach(e => {
513 | const errorFieldName = e.field;
514 | let fieldName = errorFieldName;
515 |
516 | // Handle using array validation rule.
517 | // ref: https://github.com/ant-design/ant-design/issues/14275
518 | Object.keys(allRules).some(ruleFieldName => {
519 | const rules = allRules[ruleFieldName] || [];
520 |
521 | // Exist if match rule
522 | if (ruleFieldName === errorFieldName) {
523 | fieldName = ruleFieldName;
524 | return true;
525 | }
526 |
527 | // Skip if not match array type
528 | if (
529 | rules.every(({ type }) => type !== 'array') ||
530 | errorFieldName.indexOf(`${ruleFieldName}.`) !== 0
531 | ) {
532 | return false;
533 | }
534 |
535 | // Exist if match the field name
536 | const restPath = errorFieldName.slice(ruleFieldName.length + 1);
537 | if (/^\d+$/.test(restPath)) {
538 | fieldName = ruleFieldName;
539 | return true;
540 | }
541 |
542 | return false;
543 | });
544 |
545 | const field = get(errorsGroup, fieldName);
546 | if (typeof field !== 'object' || Array.isArray(field)) {
547 | set(errorsGroup, fieldName, { errors: [] });
548 | }
549 | const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
550 | fieldErrors.push(e);
551 | });
552 | }
553 | const expired = [];
554 | const nowAllFields = {};
555 | Object.keys(allRules).forEach(name => {
556 | const fieldErrors = get(errorsGroup, name);
557 | const nowField = this.fieldsStore.getField(name);
558 | // avoid concurrency problems
559 | if (!eq(nowField.value, allValues[name])) {
560 | expired.push({
561 | name,
562 | });
563 | } else {
564 | nowField.errors = fieldErrors && fieldErrors.errors;
565 | nowField.value = allValues[name];
566 | nowField.validating = false;
567 | nowField.dirty = false;
568 | nowAllFields[name] = nowField;
569 | }
570 | });
571 | this.setFields(nowAllFields);
572 | if (callback) {
573 | if (expired.length) {
574 | expired.forEach(({ name }) => {
575 | const fieldErrors = [
576 | {
577 | message: `${name} need to revalidate`,
578 | field: name,
579 | },
580 | ];
581 | set(errorsGroup, name, {
582 | expired: true,
583 | errors: fieldErrors,
584 | });
585 | });
586 | }
587 |
588 | callback(
589 | isEmptyObject(errorsGroup) ? null : errorsGroup,
590 | this.fieldsStore.getFieldsValue(fieldNames),
591 | );
592 | }
593 | });
594 | },
595 |
596 | validateFields(ns, opt, cb) {
597 | const pending = new Promise((resolve, reject) => {
598 | const { names, options } = getParams(ns, opt, cb);
599 | let { callback } = getParams(ns, opt, cb);
600 | if (!callback || typeof callback === 'function') {
601 | const oldCb = callback;
602 | callback = (errors, values) => {
603 | if (oldCb) {
604 | oldCb(errors, values);
605 | }
606 | if (errors) {
607 | reject({ errors, values });
608 | } else {
609 | resolve(values);
610 | }
611 | };
612 | }
613 | const fieldNames = names
614 | ? this.fieldsStore.getValidFieldsFullName(names)
615 | : this.fieldsStore.getValidFieldsName();
616 | const fields = fieldNames
617 | .filter(name => {
618 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
619 | return hasRules(fieldMeta.validate);
620 | })
621 | .map(name => {
622 | const field = this.fieldsStore.getField(name);
623 | field.value = this.fieldsStore.getFieldValue(name);
624 | return field;
625 | });
626 | if (!fields.length) {
627 | callback(null, this.fieldsStore.getFieldsValue(fieldNames));
628 | return;
629 | }
630 | if (!('firstFields' in options)) {
631 | options.firstFields = fieldNames.filter(name => {
632 | const fieldMeta = this.fieldsStore.getFieldMeta(name);
633 | return !!fieldMeta.validateFirst;
634 | });
635 | }
636 | this.validateFieldsInternal(
637 | fields,
638 | {
639 | fieldNames,
640 | options,
641 | },
642 | callback,
643 | );
644 | });
645 | pending.catch(e => {
646 | // eslint-disable-next-line no-console
647 | if (console.error && process.env.NODE_ENV !== 'production') {
648 | // eslint-disable-next-line no-console
649 | console.error(e);
650 | }
651 | return e;
652 | });
653 | return pending;
654 | },
655 |
656 | isSubmitting() {
657 | if (
658 | process.env.NODE_ENV !== 'production' &&
659 | process.env.NODE_ENV !== 'test'
660 | ) {
661 | warning(
662 | false,
663 | '`isSubmitting` is deprecated. ' +
664 | "Actually, it's more convenient to handle submitting status by yourself.",
665 | );
666 | }
667 | return this.state.submitting;
668 | },
669 |
670 | submit(callback) {
671 | if (
672 | process.env.NODE_ENV !== 'production' &&
673 | process.env.NODE_ENV !== 'test'
674 | ) {
675 | warning(
676 | false,
677 | '`submit` is deprecated. ' +
678 | "Actually, it's more convenient to handle submitting status by yourself.",
679 | );
680 | }
681 | const fn = () => {
682 | this.setState({
683 | submitting: false,
684 | });
685 | };
686 | this.setState({
687 | submitting: true,
688 | });
689 | callback(fn);
690 | },
691 |
692 | render() {
693 | const { wrappedComponentRef, ...restProps } = this.props; // eslint-disable-line
694 | const formProps = {
695 | [formPropName]: this.getForm(),
696 | };
697 | if (withRef) {
698 | if (
699 | process.env.NODE_ENV !== 'production' &&
700 | process.env.NODE_ENV !== 'test'
701 | ) {
702 | warning(
703 | false,
704 | '`withRef` is deprecated, please use `wrappedComponentRef` instead. ' +
705 | 'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140',
706 | );
707 | }
708 | formProps.ref = 'wrappedComponent';
709 | } else if (wrappedComponentRef) {
710 | formProps.ref = wrappedComponentRef;
711 | }
712 | const props = mapProps.call(this, {
713 | ...formProps,
714 | ...restProps,
715 | });
716 | return ;
717 | },
718 | });
719 |
720 | return argumentContainer(unsafeLifecyclesPolyfill(Form), WrappedComponent);
721 | };
722 | }
723 |
724 | export default createBaseForm;
725 |
--------------------------------------------------------------------------------
/src/createDOMForm.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import scrollIntoView from 'dom-scroll-into-view';
3 | import has from 'lodash/has';
4 | import createBaseForm from './createBaseForm';
5 | import { mixin as formMixin } from './createForm';
6 | import { getParams } from './utils';
7 |
8 | function computedStyle(el, prop) {
9 | const getComputedStyle = window.getComputedStyle;
10 | const style =
11 | // If we have getComputedStyle
12 | getComputedStyle ?
13 | // Query it
14 | // TODO: From CSS-Query notes, we might need (node, null) for FF
15 | getComputedStyle(el) :
16 |
17 | // Otherwise, we are in IE and use currentStyle
18 | el.currentStyle;
19 | if (style) {
20 | return style
21 | [
22 | // Switch to camelCase for CSSOM
23 | // DEV: Grabbed from jQuery
24 | // https://github.com/jquery/jquery/blob/1.9-stable/src/css.js#L191-L194
25 | // https://github.com/jquery/jquery/blob/1.9-stable/src/core.js#L593-L597
26 | prop.replace(/-(\w)/gi, (word, letter) => {
27 | return letter.toUpperCase();
28 | })
29 | ];
30 | }
31 | return undefined;
32 | }
33 |
34 | function getScrollableContainer(n) {
35 | let node = n;
36 | let nodeName;
37 | /* eslint no-cond-assign:0 */
38 | while ((nodeName = node.nodeName.toLowerCase()) !== 'body') {
39 | const overflowY = computedStyle(node, 'overflowY');
40 | // https://stackoverflow.com/a/36900407/3040605
41 | if (
42 | node !== n &&
43 | (overflowY === 'auto' || overflowY === 'scroll') &&
44 | node.scrollHeight > node.clientHeight
45 | ) {
46 | return node;
47 | }
48 | node = node.parentNode;
49 | }
50 | return nodeName === 'body' ? node.ownerDocument : node;
51 | }
52 |
53 | const mixin = {
54 | getForm() {
55 | return {
56 | ...formMixin.getForm.call(this),
57 | validateFieldsAndScroll: this.validateFieldsAndScroll,
58 | };
59 | },
60 |
61 | validateFieldsAndScroll(ns, opt, cb) {
62 | const { names, callback, options } = getParams(ns, opt, cb);
63 |
64 | const newCb = (error, values) => {
65 | if (error) {
66 | const validNames = this.fieldsStore.getValidFieldsName();
67 | let firstNode;
68 | let firstTop;
69 |
70 | validNames.forEach((name) => {
71 | if (has(error, name)) {
72 | const instance = this.getFieldInstance(name);
73 | if (instance) {
74 | const node = ReactDOM.findDOMNode(instance);
75 | const top = node.getBoundingClientRect().top;
76 | if (node.type !== 'hidden' && (firstTop === undefined || firstTop > top)) {
77 | firstTop = top;
78 | firstNode = node;
79 | }
80 | }
81 | }
82 | });
83 |
84 | if (firstNode) {
85 | const c = options.container || getScrollableContainer(firstNode);
86 | scrollIntoView(firstNode, c, {
87 | onlyScrollIfNeeded: true,
88 | ...options.scroll,
89 | });
90 | }
91 | }
92 |
93 | if (typeof callback === 'function') {
94 | callback(error, values);
95 | }
96 | };
97 |
98 | return this.validateFields(names, options, newCb);
99 | },
100 | };
101 |
102 | function createDOMForm(option) {
103 | return createBaseForm({
104 | ...option,
105 | }, [mixin]);
106 | }
107 |
108 | export default createDOMForm;
109 |
--------------------------------------------------------------------------------
/src/createFieldsStore.js:
--------------------------------------------------------------------------------
1 | import set from 'lodash/set';
2 | import createFormField, { isFormField } from './createFormField';
3 | import {
4 | hasRules,
5 | flattenFields,
6 | getErrorStrs,
7 | startsWith,
8 | } from './utils';
9 |
10 | function partOf(a, b) {
11 | return b.indexOf(a) === 0 && ['.', '['].indexOf(b[a.length]) !== -1;
12 | }
13 |
14 | function internalFlattenFields(fields) {
15 | return flattenFields(
16 | fields,
17 | (_, node) => isFormField(node),
18 | 'You must wrap field data with `createFormField`.'
19 | );
20 | }
21 |
22 | class FieldsStore {
23 | constructor(fields) {
24 | this.fields = internalFlattenFields(fields);
25 | this.fieldsMeta = {};
26 | }
27 |
28 | updateFields(fields) {
29 | this.fields = internalFlattenFields(fields);
30 | }
31 |
32 | flattenRegisteredFields(fields) {
33 | const validFieldsName = this.getAllFieldsName();
34 | return flattenFields(
35 | fields,
36 | path => validFieldsName.indexOf(path) >= 0,
37 | 'You cannot set a form field before rendering a field associated with the value.'
38 | );
39 | }
40 |
41 | setFieldsInitialValue = (initialValues) => {
42 | const flattenedInitialValues = this.flattenRegisteredFields(initialValues);
43 | const fieldsMeta = this.fieldsMeta;
44 | Object.keys(flattenedInitialValues).forEach(name => {
45 | if (fieldsMeta[name]) {
46 | this.setFieldMeta(name, {
47 | ...this.getFieldMeta(name),
48 | initialValue: flattenedInitialValues[name],
49 | });
50 | }
51 | });
52 | }
53 |
54 | setFields(fields) {
55 | const fieldsMeta = this.fieldsMeta;
56 | const nowFields = {
57 | ...this.fields,
58 | ...fields,
59 | };
60 | const nowValues = {};
61 | Object.keys(fieldsMeta)
62 | .forEach((f) => {
63 | nowValues[f] = this.getValueFromFields(f, nowFields);
64 | });
65 | Object.keys(nowValues).forEach((f) => {
66 | const value = nowValues[f];
67 | const fieldMeta = this.getFieldMeta(f);
68 | if (fieldMeta && fieldMeta.normalize) {
69 | const nowValue =
70 | fieldMeta.normalize(value, this.getValueFromFields(f, this.fields), nowValues);
71 | if (nowValue !== value) {
72 | nowFields[f] = {
73 | ...nowFields[f],
74 | value: nowValue,
75 | };
76 | }
77 | }
78 | });
79 | this.fields = nowFields;
80 | }
81 |
82 | resetFields(ns) {
83 | const { fields } = this;
84 | const names = ns ?
85 | this.getValidFieldsFullName(ns) :
86 | this.getAllFieldsName();
87 | return names.reduce((acc, name) => {
88 | const field = fields[name];
89 | if (field && 'value' in field) {
90 | acc[name] = {};
91 | }
92 | return acc;
93 | }, {});
94 | }
95 |
96 | setFieldMeta(name, meta) {
97 | this.fieldsMeta[name] = meta;
98 | }
99 |
100 | setFieldsAsDirty() {
101 | Object.keys(this.fields).forEach((name) => {
102 | const field = this.fields[name];
103 | const fieldMeta = this.fieldsMeta[name];
104 | if (field && fieldMeta && hasRules(fieldMeta.validate)) {
105 | this.fields[name] = {
106 | ...field,
107 | dirty: true,
108 | };
109 | }
110 | });
111 | }
112 |
113 | getFieldMeta(name) {
114 | this.fieldsMeta[name] = this.fieldsMeta[name] || {};
115 | return this.fieldsMeta[name];
116 | }
117 |
118 | getValueFromFields(name, fields) {
119 | const field = fields[name];
120 | if (field && 'value' in field) {
121 | return field.value;
122 | }
123 | const fieldMeta = this.getFieldMeta(name);
124 | return fieldMeta && fieldMeta.initialValue;
125 | }
126 |
127 | getAllValues = () => {
128 | const { fieldsMeta, fields } = this;
129 | return Object.keys(fieldsMeta)
130 | .reduce((acc, name) => set(acc, name, this.getValueFromFields(name, fields)), {});
131 | }
132 |
133 | getValidFieldsName() {
134 | const { fieldsMeta } = this;
135 | return fieldsMeta ?
136 | Object.keys(fieldsMeta).filter(name => !this.getFieldMeta(name).hidden) :
137 | [];
138 | }
139 |
140 | getAllFieldsName() {
141 | const { fieldsMeta } = this;
142 | return fieldsMeta ? Object.keys(fieldsMeta) : [];
143 | }
144 |
145 | getValidFieldsFullName(maybePartialName) {
146 | const maybePartialNames = Array.isArray(maybePartialName) ?
147 | maybePartialName : [maybePartialName];
148 | return this.getValidFieldsName()
149 | .filter(fullName => maybePartialNames.some(partialName => (
150 | fullName === partialName || (
151 | startsWith(fullName, partialName) &&
152 | ['.', '['].indexOf(fullName[partialName.length]) >= 0
153 | )
154 | )));
155 | }
156 |
157 | getFieldValuePropValue(fieldMeta) {
158 | const { name, getValueProps, valuePropName } = fieldMeta;
159 | const field = this.getField(name);
160 | const fieldValue = 'value' in field ?
161 | field.value : fieldMeta.initialValue;
162 | if (getValueProps) {
163 | return getValueProps(fieldValue);
164 | }
165 | return { [valuePropName]: fieldValue };
166 | }
167 |
168 | getField(name) {
169 | return {
170 | ...this.fields[name],
171 | name,
172 | };
173 | }
174 |
175 | getNotCollectedFields() {
176 | const fieldsName = this.getValidFieldsName();
177 | return fieldsName
178 | .filter(name => !this.fields[name])
179 | .map(name => ({
180 | name,
181 | dirty: false,
182 | value: this.getFieldMeta(name).initialValue,
183 | }))
184 | .reduce((acc, field) => set(acc, field.name, createFormField(field)), {});
185 | }
186 |
187 | getNestedAllFields() {
188 | return Object.keys(this.fields)
189 | .reduce(
190 | (acc, name) => set(acc, name, createFormField(this.fields[name])),
191 | this.getNotCollectedFields()
192 | );
193 | }
194 |
195 | getFieldMember(name, member) {
196 | return this.getField(name)[member];
197 | }
198 |
199 | getNestedFields(names, getter) {
200 | const fields = names || this.getValidFieldsName();
201 | return fields.reduce((acc, f) => set(acc, f, getter(f)), {});
202 | }
203 |
204 | getNestedField(name, getter) {
205 | const fullNames = this.getValidFieldsFullName(name);
206 | if (
207 | fullNames.length === 0 || // Not registered
208 | (fullNames.length === 1 && fullNames[0] === name) // Name already is full name.
209 | ) {
210 | return getter(name);
211 | }
212 | const isArrayValue = fullNames[0][name.length] === '[';
213 | const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1;
214 | return fullNames
215 | .reduce(
216 | (acc, fullName) => set(
217 | acc,
218 | fullName.slice(suffixNameStartIndex),
219 | getter(fullName)
220 | ),
221 | isArrayValue ? [] : {}
222 | );
223 | }
224 |
225 | getFieldsValue = (names) => {
226 | return this.getNestedFields(names, this.getFieldValue);
227 | }
228 |
229 | getFieldValue = (name) => {
230 | const { fields } = this;
231 | return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields));
232 | }
233 |
234 | getFieldsError = (names) => {
235 | return this.getNestedFields(names, this.getFieldError);
236 | }
237 |
238 | getFieldError = (name) => {
239 | return this.getNestedField(
240 | name,
241 | (fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors'))
242 | );
243 | }
244 |
245 | isFieldValidating = (name) => {
246 | return this.getFieldMember(name, 'validating');
247 | }
248 |
249 | isFieldsValidating = (ns) => {
250 | const names = ns || this.getValidFieldsName();
251 | return names.some((n) => this.isFieldValidating(n));
252 | }
253 |
254 | isFieldTouched = (name) => {
255 | return this.getFieldMember(name, 'touched');
256 | }
257 |
258 | isFieldsTouched = (ns) => {
259 | const names = ns || this.getValidFieldsName();
260 | return names.some((n) => this.isFieldTouched(n));
261 | }
262 |
263 | // @private
264 | // BG: `a` and `a.b` cannot be use in the same form
265 | isValidNestedFieldName(name) {
266 | const names = this.getAllFieldsName();
267 | return names.every(n => !partOf(n, name) && !partOf(name, n));
268 | }
269 |
270 | clearField(name) {
271 | delete this.fields[name];
272 | delete this.fieldsMeta[name];
273 | }
274 | }
275 |
276 | export default function createFieldsStore(fields) {
277 | return new FieldsStore(fields);
278 | }
279 |
--------------------------------------------------------------------------------
/src/createForm.js:
--------------------------------------------------------------------------------
1 | import createBaseForm from './createBaseForm';
2 |
3 | export const mixin = {
4 | getForm() {
5 | return {
6 | getFieldsValue: this.fieldsStore.getFieldsValue,
7 | getFieldValue: this.fieldsStore.getFieldValue,
8 | getFieldInstance: this.getFieldInstance,
9 | setFieldsValue: this.setFieldsValue,
10 | setFields: this.setFields,
11 | setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
12 | getFieldDecorator: this.getFieldDecorator,
13 | getFieldProps: this.getFieldProps,
14 | getFieldsError: this.fieldsStore.getFieldsError,
15 | getFieldError: this.fieldsStore.getFieldError,
16 | isFieldValidating: this.fieldsStore.isFieldValidating,
17 | isFieldsValidating: this.fieldsStore.isFieldsValidating,
18 | isFieldsTouched: this.fieldsStore.isFieldsTouched,
19 | isFieldTouched: this.fieldsStore.isFieldTouched,
20 | isSubmitting: this.isSubmitting,
21 | submit: this.submit,
22 | validateFields: this.validateFields,
23 | resetFields: this.resetFields,
24 | };
25 | },
26 | };
27 |
28 | function createForm(options) {
29 | return createBaseForm(options, [mixin]);
30 | }
31 |
32 | export default createForm;
33 |
--------------------------------------------------------------------------------
/src/createFormField.js:
--------------------------------------------------------------------------------
1 | class Field {
2 | constructor(fields) {
3 | Object.assign(this, fields);
4 | }
5 | }
6 |
7 | export function isFormField(obj) {
8 | return obj instanceof Field;
9 | }
10 |
11 | export default function createFormField(field) {
12 | if (isFormField(field)) {
13 | return field;
14 | }
15 | return new Field(field);
16 | }
17 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // export this package's api
2 | import createForm from './createForm';
3 | import createFormField from './createFormField';
4 | import formShape from './propTypes';
5 |
6 | export { createFormField, formShape, createForm };
7 |
--------------------------------------------------------------------------------
/src/propTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | const formShape = PropTypes.shape({
4 | getFieldsValue: PropTypes.func,
5 | getFieldValue: PropTypes.func,
6 | getFieldInstance: PropTypes.func,
7 | setFieldsValue: PropTypes.func,
8 | setFields: PropTypes.func,
9 | setFieldsInitialValue: PropTypes.func,
10 | getFieldDecorator: PropTypes.func,
11 | getFieldProps: PropTypes.func,
12 | getFieldsError: PropTypes.func,
13 | getFieldError: PropTypes.func,
14 | isFieldValidating: PropTypes.func,
15 | isFieldsValidating: PropTypes.func,
16 | isFieldsTouched: PropTypes.func,
17 | isFieldTouched: PropTypes.func,
18 | isSubmitting: PropTypes.func,
19 | submit: PropTypes.func,
20 | validateFields: PropTypes.func,
21 | resetFields: PropTypes.func,
22 | });
23 |
24 | export default formShape;
25 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import hoistStatics from 'hoist-non-react-statics';
2 | import warning from 'warning';
3 | import { isMemo } from 'react-is';
4 |
5 | function getDisplayName(WrappedComponent) {
6 | return WrappedComponent.displayName || WrappedComponent.name || 'WrappedComponent';
7 | }
8 |
9 | export function argumentContainer(Container, WrappedComponent) {
10 | /* eslint no-param-reassign:0 */
11 | Container.displayName = `Form(${getDisplayName(WrappedComponent)})`;
12 | Container.WrappedComponent = WrappedComponent;
13 | return hoistStatics(Container, WrappedComponent);
14 | }
15 |
16 | export function identity(obj) {
17 | return obj;
18 | }
19 |
20 | export function flattenArray(arr) {
21 | return Array.prototype.concat.apply([], arr);
22 | }
23 |
24 | export function treeTraverse(path = '', tree, isLeafNode, errorMessage, callback) {
25 | if (isLeafNode(path, tree)) {
26 | callback(path, tree);
27 | } else if (tree === undefined || tree === null) {
28 | // Do nothing
29 | } else if (Array.isArray(tree)) {
30 | tree.forEach((subTree, index) => treeTraverse(
31 | `${path}[${index}]`,
32 | subTree,
33 | isLeafNode,
34 | errorMessage,
35 | callback
36 | ));
37 | } else { // It's object and not a leaf node
38 | if (typeof tree !== 'object') {
39 | warning(false, errorMessage);
40 | return;
41 | }
42 | Object.keys(tree).forEach(subTreeKey => {
43 | const subTree = tree[subTreeKey];
44 | treeTraverse(
45 | `${path}${path ? '.' : ''}${subTreeKey}`,
46 | subTree,
47 | isLeafNode,
48 | errorMessage,
49 | callback
50 | );
51 | });
52 | }
53 | }
54 |
55 | export function flattenFields(maybeNestedFields, isLeafNode, errorMessage) {
56 | const fields = {};
57 | treeTraverse(undefined, maybeNestedFields, isLeafNode, errorMessage, (path, node) => {
58 | fields[path] = node;
59 | });
60 | return fields;
61 | }
62 |
63 | export function normalizeValidateRules(validate, rules, validateTrigger) {
64 | const validateRules = validate.map((item) => {
65 | const newItem = {
66 | ...item,
67 | trigger: item.trigger || [],
68 | };
69 | if (typeof newItem.trigger === 'string') {
70 | newItem.trigger = [newItem.trigger];
71 | }
72 | return newItem;
73 | });
74 | if (rules) {
75 | validateRules.push({
76 | trigger: validateTrigger ? [].concat(validateTrigger) : [],
77 | rules,
78 | });
79 | }
80 | return validateRules;
81 | }
82 |
83 | export function getValidateTriggers(validateRules) {
84 | return validateRules
85 | .filter(item => !!item.rules && item.rules.length)
86 | .map(item => item.trigger)
87 | .reduce((pre, curr) => pre.concat(curr), []);
88 | }
89 |
90 | export function getValueFromEvent(e) {
91 | // To support custom element
92 | if (!e || !e.target) {
93 | return e;
94 | }
95 | const { target } = e;
96 | return target.type === 'checkbox' ? target.checked : target.value;
97 | }
98 |
99 | export function getErrorStrs(errors) {
100 | if (errors) {
101 | return errors.map((e) => {
102 | if (e && e.message) {
103 | return e.message;
104 | }
105 | return e;
106 | });
107 | }
108 | return errors;
109 | }
110 |
111 | export function getParams(ns, opt, cb) {
112 | let names = ns;
113 | let options = opt;
114 | let callback = cb;
115 | if (cb === undefined) {
116 | if (typeof names === 'function') {
117 | callback = names;
118 | options = {};
119 | names = undefined;
120 | } else if (Array.isArray(names)) {
121 | if (typeof options === 'function') {
122 | callback = options;
123 | options = {};
124 | } else {
125 | options = options || {};
126 | }
127 | } else {
128 | callback = options;
129 | options = names || {};
130 | names = undefined;
131 | }
132 | }
133 | return {
134 | names,
135 | options,
136 | callback,
137 | };
138 | }
139 |
140 | export function isEmptyObject(obj) {
141 | return Object.keys(obj).length === 0;
142 | }
143 |
144 | export function hasRules(validate) {
145 | if (validate) {
146 | return validate.some((item) => {
147 | return item.rules && item.rules.length;
148 | });
149 | }
150 | return false;
151 | }
152 |
153 | export function startsWith(str, prefix) {
154 | return str.lastIndexOf(prefix, 0) === 0;
155 | }
156 |
157 | export function supportRef(nodeOrComponent) {
158 | const type = isMemo(nodeOrComponent)
159 | ? nodeOrComponent.type.type
160 | : nodeOrComponent.type;
161 |
162 | // Function component node
163 | if (typeof type === 'function' && !(type.prototype && type.prototype.render)) {
164 | return false;
165 | }
166 |
167 | // Class component
168 | if (
169 | typeof nodeOrComponent === 'function' &&
170 | !(nodeOrComponent.prototype && nodeOrComponent.prototype.render)
171 | ) {
172 | return false;
173 | }
174 |
175 | return true;
176 | }
177 |
--------------------------------------------------------------------------------
/tests/FieldElemWrapper.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import FieldElemWrapper from '../src/FieldElemWrapper';
4 |
5 | describe('FieldElemWrapper', () => {
6 | it('should register field when mount and clear field when unmount', () => {
7 | const name = 'field-name';
8 | const mockForm = {
9 | domFields: {},
10 | recoverClearedField: jest.fn(),
11 | fieldsStore: {
12 | getFieldMeta: jest.fn(() => ({})),
13 | getField: jest.fn(() => ({})),
14 | },
15 | clearedFieldMetaCache: {},
16 | clearField: jest.fn(),
17 | };
18 | const children = children;
19 | const wrapper = mount(
20 |
21 | {children}
22 | ,
23 | );
24 |
25 | expect(wrapper.children().get(0)).toEqual(children);
26 | expect(mockForm.domFields[name]).toBe(true);
27 | expect(mockForm.recoverClearedField).toBeCalledWith(name);
28 |
29 | const mockFieldMeta = {};
30 | const mockField = {};
31 | mockForm.fieldsStore.getFieldMeta.mockReturnValueOnce(mockFieldMeta);
32 | mockForm.fieldsStore.getField.mockReturnValueOnce(mockField);
33 | wrapper.unmount();
34 | expect(mockForm.clearedFieldMetaCache[name]).toEqual({
35 | field: mockField,
36 | meta: mockFieldMeta,
37 | });
38 | expect(mockForm.clearField).toBeCalledWith(name);
39 | expect(mockForm.domFields[name]).toBe(undefined);
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/tests/async-validation.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-render-return-value */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { Simulate } from 'react-dom/test-utils';
6 | import async from 'async';
7 | import createForm from '../src/createForm';
8 |
9 | class Test extends React.Component {
10 | check = (rule, value, callback) => {
11 | setTimeout(() => {
12 | if (value === '1') {
13 | callback();
14 | } else {
15 | callback(new Error('must be 1'));
16 | }
17 | }, 100);
18 | }
19 |
20 | render() {
21 | const { getFieldProps } = this.props.form;
22 | return (
23 |
24 |
25 |
30 |
31 | );
32 | }
33 | }
34 |
35 | Test = createForm({
36 | withRef: true,
37 | })(Test);
38 |
39 | describe('Async Validation', () => {
40 | let container;
41 | let component;
42 | let form;
43 |
44 | beforeEach(() => {
45 | container = document.createElement('div');
46 | document.body.appendChild(container);
47 | component = ReactDOM.render(, container);
48 | component = component.refs.wrappedComponent;
49 | form = component.props.form;
50 | });
51 |
52 | afterEach(() => {
53 | ReactDOM.unmountComponentAtNode(container);
54 | document.body.removeChild(container);
55 | });
56 |
57 | it('works', (done) => {
58 | async.series([
59 | (callback) => {
60 | Simulate.change(form.getFieldInstance('async'));
61 | expect(form.getFieldValue('async')).toBe('');
62 | expect(form.getFieldError('async')).toBeFalsy();
63 | expect(form.isFieldValidating('async')).toBe(true);
64 | expect(form.isFieldsValidating()).toBe(true);
65 | setTimeout(callback, 200);
66 | },
67 | (callback) => {
68 | expect(form.getFieldError('async')).toEqual(['must be 1']);
69 | expect(form.isFieldValidating('async')).toBe(false);
70 | expect(form.isFieldsValidating()).toBe(false);
71 | callback();
72 | },
73 | (callback) => {
74 | form.getFieldInstance('async').value = '1';
75 | Simulate.change(form.getFieldInstance('async'));
76 | expect(form.getFieldValue('async')).toBe('1');
77 | expect(form.getFieldError('async')).toBe(undefined);
78 | expect(form.isFieldValidating('async')).toBe(true);
79 | expect(form.isFieldsValidating()).toBe(true);
80 | setTimeout(callback, 200);
81 | },
82 | (callback) => {
83 | expect(form.getFieldError('async')).toBeFalsy();
84 | expect(form.isFieldValidating('async')).toBe(false);
85 | expect(form.isFieldsValidating()).toBe(false);
86 | callback();
87 | },
88 | ], done);
89 | });
90 | it('validateFields works for error', (done) => {
91 | form.validateFields((errors, values) => {
92 | expect(values.normal).toBe(undefined);
93 | expect(values.async).toBe(undefined);
94 | expect(Object.keys(errors).length).toBe(1);
95 | expect(errors.async.errors.map(e => e.message)).toEqual(['must be 1']);
96 | done();
97 | });
98 | });
99 |
100 | it('validateFields works for ok', (done) => {
101 | form.getFieldInstance('async').value = '1';
102 | Simulate.change(form.getFieldInstance('async'));
103 | form.validateFields((errors, values) => {
104 | expect(values.normal).toBe(undefined);
105 | expect(values.async).toBe('1');
106 | expect(errors).toBeFalsy();
107 | done();
108 | });
109 | });
110 |
111 | it('promise validateFields works for ok', (done) => {
112 | form.getFieldInstance('async').value = '1';
113 | Simulate.change(form.getFieldInstance('async'));
114 | return form.validateFields()
115 | .then(values => {
116 | expect(values.normal).toBe(undefined);
117 | expect(values.async).toBe('1');
118 | done();
119 | });
120 | });
121 |
122 | it('will error if change when validating', (done) => {
123 | form.validateFields((errors) => {
124 | expect(Object.keys(errors).length).toBe(1);
125 | expect(errors.async.errors.map(e => e.message)).toEqual(['async need to revalidate']);
126 | setTimeout(() => {
127 | done();
128 | }, 500);
129 | });
130 | form.getFieldInstance('async').value = '1';
131 | Simulate.change(form.getFieldInstance('async'));
132 | });
133 |
134 | it('Whether to catch when an error occurs', (done) => {
135 | form.validateFields()
136 | .catch(({ errors }) => {
137 | expect(Object.keys(errors).length).toBe(1);
138 | expect(errors.async.errors.map(e => e.message)).toEqual(['async need to revalidate']);
139 | expect.assertions(2);
140 | done();
141 | });
142 | form.getFieldInstance('async').value = '1';
143 | Simulate.change(form.getFieldInstance('async'));
144 | });
145 |
146 | // TODO: submit and isSubmitting are deprecated
147 | it('submit works', (done) => {
148 | expect(form.isSubmitting()).toBe(false);
149 | form.submit((callback) => {
150 | expect(form.isSubmitting()).toBe(true);
151 | setTimeout(() => {
152 | callback();
153 | expect(form.isSubmitting()).toBe(false);
154 | done();
155 | }, 100);
156 | });
157 | });
158 | });
159 |
--------------------------------------------------------------------------------
/tests/clean-field.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { mount } from 'enzyme';
6 | import createDOMForm from '../src/createDOMForm';
7 |
8 | jest.mock('dom-scroll-into-view', () => jest.fn());
9 |
10 | describe('clean field', () => {
11 | let div;
12 |
13 | beforeEach(() => {
14 | div = document.createElement('div');
15 | });
16 |
17 | afterEach(() => {
18 | ReactDOM.unmountComponentAtNode(div);
19 | });
20 |
21 | // https://github.com/ant-design/ant-design/issues/12560
22 | describe('clean field if did update removed', () => {
23 | // Prepare
24 | const Popup = ({ visible, children }) => {
25 | if (!visible) return null;
26 | return (
27 |
28 | {children}
29 |
30 | );
31 | };
32 |
33 | class Demo extends React.Component {
34 | render() {
35 | const { init, show } = this.props;
36 | const { getFieldDecorator } = this.props.form;
37 |
38 | let name;
39 | let age;
40 |
41 | if (init) {
42 | name = (
43 |
44 | name:
45 | {getFieldDecorator('name', {
46 | rules: [{ required: true }],
47 | })()}
48 |
49 | );
50 | } else {
51 | age = (
52 |
53 | age:
54 | {getFieldDecorator('age', {
55 | rules: [{ required: true }],
56 | })()}
57 |
58 | );
59 | }
60 |
61 | return (
62 |
63 | {name}
64 | {age}
65 |
66 | );
67 | }
68 | }
69 |
70 | const FormDemo = createDOMForm({
71 | withRef: true,
72 | })(Demo);
73 |
74 | class Test extends React.Component {
75 | state = {
76 | show: false,
77 | init: false,
78 | };
79 |
80 | onClick = () => {
81 | this.setState({ show: true });
82 | this.setState({ init: true });
83 | };
84 |
85 | setRef = (demo) => {
86 | this.demo = demo;
87 | };
88 |
89 | render() {
90 | const { show, init } = this.state;
91 | return (
92 |
93 |
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | // Do the test
101 | it('Remove when mount', () => {
102 | const wrapper = mount(, { attachTo: div });
103 | const form = wrapper.find('Demo').props().form;
104 | form.validateFields((error) => {
105 | expect(Object.keys(error)).toEqual(['age']);
106 | });
107 |
108 | wrapper.find('.my-btn').simulate('click');
109 |
110 | form.validateFields((error) => {
111 | expect(Object.keys(error)).toEqual(['name']);
112 | });
113 | });
114 | });
115 |
116 | // https://github.com/react-component/form/issues/205
117 | describe('Do not clean if field dom exist', () => {
118 | class Demo extends React.Component {
119 | state = {
120 | visible: true,
121 | };
122 |
123 | componentWillMount() {
124 | this.tmp = this.props.form.getFieldDecorator('age', {
125 | rules: [{ required: true }],
126 | });
127 | }
128 |
129 | render() {
130 | const { visible } = this.state;
131 | return (
132 |
133 | {visible && this.tmp()}
134 |
135 | );
136 | }
137 | }
138 |
139 | const FormDemo = createDOMForm({
140 | withRef: true,
141 | })(Demo);
142 |
143 | // Do the test
144 | it('Remove when mount', () => {
145 | const wrapper = mount(, { attachTo: div });
146 | const form = wrapper.find('Demo').props().form;
147 |
148 | // Init
149 | form.validateFields((error) => {
150 | expect(Object.keys(error)).toEqual(['age']);
151 | });
152 |
153 | // Refresh
154 | wrapper.update();
155 | form.validateFields((error) => {
156 | expect(Object.keys(error)).toEqual(['age']);
157 | });
158 |
159 | // Hide
160 | wrapper.find('Demo').setState({ visible: false });
161 | form.validateFields((error) => {
162 | expect(Object.keys(error || {})).toEqual([]);
163 | });
164 | });
165 | });
166 |
167 | describe('Don\'t clean field when has \'{ preserve: ture }\'', () => {
168 | // Prepare
169 | const Popup = ({ visible, children }) => {
170 | if (!visible) return null;
171 | return (
172 |
173 | {children}
174 |
175 | );
176 | };
177 |
178 | class Demo extends React.Component {
179 | render() {
180 | const { init, show } = this.props;
181 | const { getFieldDecorator } = this.props.form;
182 |
183 | let name;
184 | let age;
185 |
186 | if (init) {
187 | name = (
188 |
189 | name:
190 | {getFieldDecorator('name', {
191 | rules: [{ required: true }],
192 | })()}
193 |
194 | );
195 | } else {
196 | age = (
197 |
198 | age:
199 | {getFieldDecorator('age', {
200 | rules: [{ required: true }],
201 | preserve: true,
202 | })()}
203 |
204 | );
205 | }
206 |
207 | return (
208 |
209 | {name}
210 | {age}
211 |
212 | );
213 | }
214 | }
215 |
216 | const FormDemo = createDOMForm({
217 | withRef: true,
218 | })(Demo);
219 |
220 | class Test extends React.Component {
221 | state = {
222 | show: false,
223 | init: false,
224 | };
225 |
226 | onClick = () => {
227 | this.setState({ show: true });
228 | this.setState({ init: true });
229 | };
230 |
231 | setRef = (demo) => {
232 | this.demo = demo;
233 | };
234 |
235 | render() {
236 | const { show, init } = this.state;
237 | return (
238 |
239 |
240 |
241 |
242 | );
243 | }
244 | }
245 |
246 | // Do the test
247 | it('Don\'t remove when mount', done => {
248 | const wrapper = mount(, { attachTo: div });
249 | const form = wrapper.find('Demo').props().form;
250 | form.validateFields((error) => {
251 | expect(Object.keys(error)).toEqual(['age']);
252 | });
253 |
254 | wrapper.find('.my-btn').simulate('click');
255 |
256 | form.validateFields((error) => {
257 | expect(Object.keys(error)).toEqual(['age', 'name']);
258 | done();
259 | });
260 | });
261 | });
262 | });
263 |
--------------------------------------------------------------------------------
/tests/createDOMForm.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/prefer-stateless-function */
2 | import React from 'react';
3 | import { mount } from 'enzyme';
4 | import scrollIntoView from 'dom-scroll-into-view';
5 | import createDOMForm from '../src/createDOMForm';
6 |
7 | jest.mock('dom-scroll-into-view', () => jest.fn());
8 |
9 | class Test extends React.Component {
10 | render() {
11 | const { form, name } = this.props;
12 | const { getFieldDecorator } = form;
13 | return (
14 |
15 | {getFieldDecorator(name, {
16 | rules: [{
17 | required: true,
18 | }],
19 | })()}
20 |
21 | );
22 | }
23 | }
24 |
25 | Test = createDOMForm({
26 | withRef: true,
27 | })(Test);
28 |
29 | describe('validateFieldsAndScroll', () => {
30 | beforeEach(() => {
31 | scrollIntoView.mockClear();
32 | });
33 | it('works on overflowY auto element', (done) => {
34 | const wrapper = mount(, { attachTo: document.body });
35 | const form = wrapper.ref('wrappedComponent').props.form;
36 | form.validateFieldsAndScroll(() => {
37 | expect(scrollIntoView.mock.calls[0][1].tagName).not.toBe('TEXTAREA');
38 | wrapper.detach();
39 | done();
40 | });
41 | });
42 |
43 | it('works with nested fields', (done) => {
44 | const wrapper = mount(, { attachTo: document.body });
45 | const form = wrapper.ref('wrappedComponent').props.form;
46 | form.validateFieldsAndScroll(() => {
47 | expect(scrollIntoView).toHaveBeenCalled();
48 | wrapper.detach();
49 | done();
50 | });
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/tests/createForm.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, space-before-keywords, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function, react/no-string-refs */
3 |
4 | import React from 'react';
5 | import { mount } from 'enzyme';
6 | import createForm from '../src/createForm';
7 | import createFormField from '../src/createFormField';
8 |
9 | describe('validateMessage', () => {
10 | it('works', () => {
11 | class Test extends React.Component {
12 | render() {
13 | const { getFieldProps } = this.props.form;
14 | return (
15 |
16 |
22 |
23 | );
24 | }
25 | }
26 | Test = createForm({
27 | withRef: true,
28 | validateMessages: {
29 | required: '%s required!',
30 | },
31 | })(Test);
32 | const wrapper = mount();
33 | const form = wrapper.ref('wrappedComponent').props.form;
34 | wrapper.find('input').simulate('change');
35 | expect(form.getFieldError('required').length).toBe(1);
36 | expect(form.getFieldError('required')[0]).toBe('required required!');
37 | });
38 | });
39 |
40 | describe('onFieldsChange', () => {
41 | it('trigger `onFieldsChange` when value change', () => {
42 | const onFieldsChange = jest.fn();
43 | const Test = createForm({
44 | onFieldsChange,
45 | })(class extends React.Component {
46 | render() {
47 | const { getFieldProps } = this.props.form;
48 | return (
49 |
54 | );
55 | }
56 | });
57 | const wrapper = mount();
58 |
59 | wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } });
60 | expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } });
61 | expect(onFieldsChange.mock.calls[0][2]).toMatchObject({
62 | user: {
63 | name: { value: 'Benjy' },
64 | age: { value: undefined },
65 | },
66 | agreement: { value: undefined },
67 | });
68 | });
69 |
70 | it('trigger `onFieldsChange` when `setFields`', () => {
71 | const onFieldsChange = jest.fn();
72 | const Test = createForm({
73 | withRef: true,
74 | onFieldsChange,
75 | })(class extends React.Component {
76 | render() {
77 | const { getFieldProps } = this.props.form;
78 | return (
79 |
84 | );
85 | }
86 | });
87 | const wrapper = mount();
88 | const form = wrapper.ref('wrappedComponent').props.form;
89 |
90 | form.setFields({ user: { name: { value: 'Benjy' } } });
91 | expect(onFieldsChange.mock.calls[0][1]).toMatchObject({ user: { name: { value: 'Benjy' } } });
92 | expect(onFieldsChange.mock.calls[0][2]).toMatchObject({
93 | user: {
94 | name: { value: 'Benjy' },
95 | age: { value: undefined },
96 | },
97 | agreement: { value: undefined },
98 | });
99 | });
100 |
101 | it('fields in arguemnts can be passed to `mapPropsToFields` directly', () => {
102 | const Test = createForm({
103 | withRef: true,
104 | onFieldsChange(props, changed, all) {
105 | props.onChange(all);
106 | },
107 | mapPropsToFields({ fields }) {
108 | return fields;
109 | },
110 | })(class extends React.Component {
111 | render() {
112 | const { getFieldProps } = this.props.form;
113 | return (
114 |
119 | );
120 | }
121 | });
122 | class TestWrapper extends React.Component {
123 | state = {
124 | fields: {},
125 | };
126 | handleFieldsChange = (fields) => {
127 | this.setState({ fields });
128 | }
129 | render() {
130 | return (
131 |
136 | );
137 | }
138 | }
139 | const wrapper = mount();
140 | wrapper.find('input').at(0).simulate('change', { target: { value: 'Benjy' } });
141 | wrapper.find('input').at(1).simulate('change', { target: { value: 18 } });
142 | wrapper.find('input').at(2)
143 | .simulate('change', { target: { type: 'checkbox', checked: true } });
144 | expect(wrapper.state('fields')).toMatchObject({
145 | user: {
146 | age: { value: 18 },
147 | name: { value: 'Benjy' },
148 | },
149 | agreement: { value: true },
150 | });
151 | });
152 | });
153 |
154 | describe('onValuesChange', () => {
155 | it('trigger `onValuesChange` when value change', () => {
156 | const onValuesChange = jest.fn();
157 | const Test = createForm({
158 | onValuesChange,
159 | })(class extends React.Component {
160 | render() {
161 | const { getFieldProps } = this.props.form;
162 | return (
163 |
168 | );
169 | }
170 | });
171 | const wrapper = mount();
172 | wrapper.find('input').first().simulate('change', { target: { value: 'Benjy' } });
173 | expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } });
174 | expect(onValuesChange.mock.calls[0][2])
175 | .toMatchObject({
176 | user: {
177 | name: 'Benjy',
178 | age: undefined,
179 | },
180 | agreement: undefined,
181 | });
182 | });
183 |
184 | it('trigger `onValuesChange` when `setFieldsValue`', () => {
185 | const onValuesChange = jest.fn();
186 | const Test = createForm({
187 | withRef: true,
188 | onValuesChange,
189 | })(class extends React.Component {
190 | render() {
191 | const { getFieldProps } = this.props.form;
192 | return (
193 |
198 | );
199 | }
200 | });
201 | const wrapper = mount();
202 | const form = wrapper.ref('wrappedComponent').props.form;
203 | form.setFieldsValue({ user: { name: 'Benjy' } });
204 | expect(onValuesChange.mock.calls[0][1]).toMatchObject({ user: { name: 'Benjy' } });
205 | expect(onValuesChange.mock.calls[0][2])
206 | .toMatchObject({
207 | user: {
208 | name: 'Benjy',
209 | age: undefined,
210 | },
211 | agreement: undefined,
212 | });
213 | });
214 | });
215 |
216 | describe('mapProps', () => {
217 | it('works', () => {
218 | const Test = createForm({
219 | withRef: true,
220 | mapProps(props) {
221 | return {
222 | ...props,
223 | x: props.x + 1,
224 | };
225 | },
226 | })(class extends React.Component { render() { return null; } });
227 | const wrapper = mount();
228 | const wrappedComponent = wrapper.ref('wrappedComponent');
229 | expect(wrappedComponent.props.x).toBe(3);
230 | });
231 | });
232 |
233 | describe('mapPropsToFields', () => {
234 | it('works', () => {
235 | const Test = createForm({
236 | withRef: true,
237 | mapPropsToFields({ formState }) {
238 | return {
239 | user: {
240 | name: createFormField({
241 | value: formState.userName,
242 | }),
243 | age: createFormField({
244 | value: formState.userAge,
245 | }),
246 | },
247 | agreement: createFormField({
248 | value: formState.agreement,
249 | }),
250 | };
251 | },
252 | })(class extends React.Component {
253 | render() {
254 | const { form } = this.props;
255 | const { getFieldProps } = form;
256 | return (
257 |
262 | );
263 | }
264 | });
265 | const wrapper = mount(
266 |
267 | );
268 | const form = wrapper.ref('wrappedComponent').props.form;
269 | expect(form.getFieldValue('user.name')).toBe('Benjy');
270 | expect(form.getFieldValue('user.age')).toBe(18);
271 | expect(form.getFieldValue('agreement')).toBe(false);
272 |
273 | wrapper.setProps({ formState: { userName: 'Benjy', userAge: 18, agreement: true } });
274 | expect(form.getFieldValue('user.name')).toBe('Benjy');
275 | expect(form.getFieldValue('user.age')).toBe(18);
276 | expect(form.getFieldValue('agreement')).toBe(true);
277 | });
278 |
279 | it('returned value will replace current fields', () => {
280 | const Test = createForm({
281 | withRef: true,
282 | mapPropsToFields(props) {
283 | return {
284 | field1: createFormField({
285 | value: props.formState.field1,
286 | }),
287 | };
288 | },
289 | })(class extends React.Component {
290 | render() {
291 | const { getFieldProps } = this.props.form;
292 | return (
293 |
297 | );
298 | }
299 | });
300 | const wrapper = mount();
301 | const form = wrapper.ref('wrappedComponent').props.form;
302 | wrapper.find('input').last().simulate('change', { target: { value: '3' } });
303 | wrapper.setProps({ formState: { field1: '1' } });
304 | expect(form.getFieldValue('field1')).toBe('1');
305 | expect(form.getFieldValue('field2')).toBe(undefined);
306 | });
307 | });
308 |
--------------------------------------------------------------------------------
/tests/dynamic-binding.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, space-before-keywords, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 |
4 | import React from 'react';
5 | import { mount } from 'enzyme';
6 | import createForm from '../src/createForm';
7 |
8 | describe('binding dynamic fields without any errors', () => {
9 | it('normal input', (done) => {
10 | const Test = createForm({
11 | withRef: true,
12 | })(
13 | class extends React.Component {
14 | render() {
15 | const { form, mode } = this.props;
16 | const { getFieldDecorator } = form;
17 | return (
18 |
27 | );
28 | }
29 | }
30 | );
31 | const wrapper = mount();
32 | const form = wrapper.ref('wrappedComponent').props.form;
33 | wrapper.find('#text').simulate('change', { target: { value: '123' } });
34 | wrapper.setProps({ mode: false });
35 | expect(wrapper.find('#number').getDOMNode().value).toBe('123');
36 | expect(form.getFieldValue('name')).toBe('123');
37 | wrapper.find('#number').simulate('change', { target: { value: '456' } });
38 | wrapper.setProps({ mode: true });
39 | expect(wrapper.find('#text').getDOMNode().value).toBe('456');
40 | expect(form.getFieldValue('name')).toBe('456');
41 | form.validateFields((errors, values) => {
42 | expect(errors).toBe(null);
43 | expect(values.name).toBe('456');
44 | done();
45 | });
46 | });
47 |
48 | it('hidden input', (done) => {
49 | const Test = createForm({
50 | withRef: true,
51 | })(
52 | class extends React.Component {
53 | render() {
54 | const { form, mode } = this.props;
55 | const { getFieldDecorator } = form;
56 | return (
57 |
66 | );
67 | }
68 | }
69 | );
70 | const wrapper = mount();
71 | const form = wrapper.ref('wrappedComponent').props.form;
72 | wrapper.find('#text1').simulate('change', { target: { value: '123' } });
73 | wrapper.find('#text2').simulate('change', { target: { value: '456' } });
74 | expect(wrapper.find('#text1').getDOMNode().value).toBe('123');
75 | expect(wrapper.find('#text2').getDOMNode().value).toBe('456');
76 | expect(form.getFieldValue('input1')).toBe('123');
77 | expect(form.getFieldValue('input2')).toBe('456');
78 | wrapper.setProps({ mode: false });
79 | expect(form.getFieldValue('input1')).toBe(undefined);
80 | expect(form.getFieldValue('input2')).toBe(undefined);
81 | wrapper.setProps({ mode: true });
82 | expect(wrapper.find('#text1').getDOMNode().value).toBe('');
83 | expect(wrapper.find('#text2').getDOMNode().value).toBe('');
84 | expect(form.getFieldValue('input1')).toBe(undefined);
85 | expect(form.getFieldValue('input2')).toBe(undefined);
86 | wrapper.find('#text1').simulate('change', { target: { value: '789' } });
87 | expect(wrapper.find('#text1').getDOMNode().value).toBe('789');
88 | expect(wrapper.find('#text2').getDOMNode().value).toBe('');
89 | expect(form.getFieldValue('input1')).toBe('789');
90 | expect(form.getFieldValue('input2')).toBe(undefined);
91 | form.validateFields((errors, values) => {
92 | expect(errors).toBe(null);
93 | expect(values.input1).toBe('789');
94 | expect(values.input2).toBe(undefined);
95 | done();
96 | });
97 | });
98 |
99 | it('nested fields', (done) => {
100 | const Test = createForm({
101 | withRef: true,
102 | })(
103 | class extends React.Component {
104 | render() {
105 | const { form, mode } = this.props;
106 | const { getFieldDecorator } = form;
107 | return (
108 |
113 | );
114 | }
115 | }
116 | );
117 | const wrapper = mount();
118 | const form = wrapper.ref('wrappedComponent').props.form;
119 | wrapper.find('#text').simulate('change', { target: { value: '123' } });
120 | wrapper.setProps({ mode: false });
121 | expect(wrapper.find('#number').getDOMNode().value).toBe('123');
122 | expect(form.getFieldValue('name.xxx')).toBe('123');
123 | wrapper.find('#number').simulate('change', { target: { value: '456' } });
124 | wrapper.setProps({ mode: true });
125 | expect(wrapper.find('#text').getDOMNode().value).toBe('456');
126 | expect(form.getFieldValue('name.xxx')).toBe('456');
127 |
128 | form.validateFields((errors, values) => {
129 | expect(errors).toBe(null);
130 | expect(values.name.xxx).toBe('456');
131 | done();
132 | });
133 | });
134 |
135 | it('input with different keys', (done) => {
136 | const Test = createForm({
137 | withRef: true,
138 | })(
139 | class extends React.Component {
140 | render() {
141 | const { form, mode } = this.props;
142 | const { getFieldDecorator } = form;
143 | return (
144 |
150 | );
151 | }
152 | }
153 | );
154 | const wrapper = mount();
155 | const form = wrapper.ref('wrappedComponent').props.form;
156 | wrapper.find('#text').simulate('change', { target: { value: '123' } });
157 | wrapper.setProps({ mode: false });
158 | expect(wrapper.find('#number').getDOMNode().value).toBe('123');
159 | expect(form.getFieldValue('name')).toBe('123');
160 | wrapper.find('#number').simulate('change', { target: { value: '456' } });
161 | wrapper.setProps({ mode: true });
162 | expect(wrapper.find('#text').getDOMNode().value).toBe('456');
163 | expect(form.getFieldValue('name')).toBe('456');
164 | form.validateFields((errors, values) => {
165 | expect(errors).toBe(null);
166 | expect(values.name).toBe('456');
167 | done();
168 | });
169 | });
170 |
171 | it('submit without removed fields', (done) => {
172 | const Test = createForm({
173 | withRef: true,
174 | })(
175 | class extends React.Component {
176 | render() {
177 | const { form, mode } = this.props;
178 | const { getFieldDecorator } = form;
179 | return (
180 |
186 | );
187 | }
188 | }
189 | );
190 | const wrapper = mount();
191 | const form = wrapper.ref('wrappedComponent').props.form;
192 | form.validateFields((errors, values) => {
193 | expect(errors).toBe(null);
194 | expect('name1' in values).toBe(true);
195 | expect('name2' in values).toBe(true);
196 | expect('name3' in values).toBe(true);
197 | expect('name4' in values).toBe(true);
198 | wrapper.setProps({ mode: true });
199 | form.validateFields((errors2, values2) => {
200 | expect(errors2).toBe(null);
201 | expect('name1' in values2).toBe(true);
202 | expect('name2' in values2).toBe(true);
203 | expect('name3' in values2).toBe(false);
204 | expect('name4' in values2).toBe(false);
205 | done();
206 | });
207 | });
208 | });
209 |
210 | it('reset fields', (done) => {
211 | const Test = createForm({
212 | withRef: true,
213 | })(
214 | class extends React.Component {
215 | render() {
216 | const { form, mode } = this.props;
217 | const { getFieldDecorator } = form;
218 | return (
219 |
228 | );
229 | }
230 | }
231 | );
232 | const wrapper = mount();
233 | const form = wrapper.ref('wrappedComponent').props.form;
234 | wrapper.find('#text1').simulate('change', { target: { value: '123' } });
235 | wrapper.find('#text2').simulate('change', { target: { value: '456' } });
236 | expect(wrapper.find('#text1').getDOMNode().value).toBe('123');
237 | expect(wrapper.find('#text2').getDOMNode().value).toBe('456');
238 | expect(form.getFieldValue('input1')).toBe('123');
239 | expect(form.getFieldValue('input2')).toBe('456');
240 | wrapper.setProps({ mode: false });
241 | expect(form.getFieldValue('input1')).toBe(undefined);
242 | expect(form.getFieldValue('input2')).toBe(undefined);
243 | form.resetFields();
244 | wrapper.setProps({ mode: true });
245 | expect(wrapper.find('#text1').getDOMNode().value).toBe('');
246 | expect(wrapper.find('#text2').getDOMNode().value).toBe('');
247 | expect(form.getFieldValue('input1')).toBe(undefined);
248 | expect(form.getFieldValue('input2')).toBe(undefined);
249 | wrapper.find('#text1').simulate('change', { target: { value: '789' } });
250 | expect(wrapper.find('#text1').getDOMNode().value).toBe('789');
251 | expect(wrapper.find('#text2').getDOMNode().value).toBe('');
252 | expect(form.getFieldValue('input1')).toBe('789');
253 | expect(form.getFieldValue('input2')).toBe(undefined);
254 | wrapper.find('#text2').simulate('change', { target: { value: '456' } });
255 | expect(wrapper.find('#text2').getDOMNode().value).toBe('456');
256 | expect(form.getFieldValue('input2')).toBe('456');
257 | form.validateFields((errors, values) => {
258 | expect(errors).toBe(null);
259 | expect(values.input1).toBe('789');
260 | expect(values.input2).toBe('456');
261 | done();
262 | });
263 | });
264 | });
265 |
--------------------------------------------------------------------------------
/tests/dynamic-rule.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 | import React from 'react';
4 | import { mount } from 'enzyme';
5 | import createDOMForm from '../src/createDOMForm';
6 |
7 | // https://github.com/ant-design/ant-design/issues/13689
8 | describe('reset form validate when rule changed', () => {
9 |
10 | class Demo extends React.Component {
11 | render() {
12 | const { form } = this.props;
13 | const { getFieldDecorator } = form;
14 | const type = form.getFieldValue("type");
15 |
16 | return (
17 |
18 | {getFieldDecorator('type')(
19 |
20 | )}
21 | {getFieldDecorator("val1", {
22 | rules: [{ required: type }]
23 | })(
24 |
25 | )}
26 | {getFieldDecorator("val2", {
27 | rules: [{ required: !type }]
28 | })(
29 |
30 | )}
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | const FormDemo = createDOMForm({
38 | withRef: true,
39 | })(Demo);
40 |
41 | // Do the test
42 | it('should update errors', (done) => {
43 | const wrapper = mount(, { attachTo: document.body });
44 | const form = wrapper.find('Demo').props().form;
45 |
46 | // type => test
47 | wrapper.find('.type').simulate('change', {target: {value: 'test'}});
48 | form.validateFields(err => {
49 | expect(Object.keys(err)).toEqual(['val1']);
50 |
51 | // type => ''
52 | wrapper.find('.type').simulate('change', {target: {value: ''}});
53 |
54 | form.validateFields(err2 => {
55 | expect(Object.keys(err2)).toEqual(['val2']);
56 | done();
57 | });
58 | });
59 | });
60 | });
--------------------------------------------------------------------------------
/tests/form.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 |
4 | import React from 'react';
5 | import { mount } from 'enzyme';
6 | import createForm from '../src/createForm';
7 |
8 | describe('setFieldsValue', () => {
9 | // https://github.com/ant-design/ant-design/issues/8386
10 | it('should work even set with undefined name', () => {
11 | const Test = createForm({ withRef: true })(
12 | class extends React.Component {
13 | componentDidMount() {
14 | this.props.form.setFieldsValue({
15 | normal: '2',
16 | inexist: 'oh',
17 | });
18 | }
19 |
20 | render() {
21 | const { getFieldProps } = this.props.form;
22 | return ;
23 | }
24 | }
25 | );
26 | const wrapper = mount();
27 | const form = wrapper.ref('wrappedComponent').props.form;
28 | expect(form.getFieldValue('normal')).toBe('2');
29 | });
30 | });
31 |
32 | describe('resetFields', () => {
33 | it('can reset hidden fields', () => {
34 | const Test = createForm({ withRef: true })(
35 | class extends React.Component {
36 | render() {
37 | const { getFieldProps } = this.props.form;
38 | return (
39 |
45 | );
46 | }
47 | }
48 | );
49 | const wrapper = mount();
50 | const form = wrapper.ref('wrappedComponent').props.form;
51 | form.setFieldsValue({ normal: 'Hello world!' });
52 | expect(form.getFieldsValue(['normal'])).toEqual({ normal: 'Hello world!' });
53 | form.resetFields();
54 | expect(form.getFieldsValue(['normal'])).toEqual({ normal: '' });
55 | });
56 | });
57 |
58 | describe('form name', () => {
59 | it('set id', () => {
60 | const Test = createForm({
61 | fieldNameProp: 'id',
62 | name: 'test',
63 | })(
64 | class extends React.Component {
65 | render() {
66 | const { getFieldProps } = this.props.form;
67 | return (
68 |
71 | );
72 | }
73 | }
74 | );
75 | const wrapper = mount();
76 | const input = wrapper.find('input').instance();
77 | expect(input.id).toBe('test_user');
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/tests/getFieldDecorator.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp, react/no-render-return-value */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { Simulate } from 'react-dom/test-utils';
6 | import createForm from '../src/createForm';
7 |
8 | describe('getFieldDecorator', () => {
9 | class Test extends React.Component {
10 | componentWillMount() {
11 | const { getFieldDecorator } = this.props.form;
12 | this.normalInput = getFieldDecorator('normal');
13 | this.requiredInput = getFieldDecorator('required', {
14 | rules: [{
15 | required: true,
16 | }],
17 | });
18 | this.blurRequiredInput = getFieldDecorator('blurRequired', {
19 | validate: [{
20 | trigger: 'onBlur',
21 | rules: [{
22 | required: true,
23 | }],
24 | }],
25 | });
26 | }
27 | render() {
28 | return (
29 | {this.normalInput(
30 |
31 | )}
32 |
33 | {this.requiredInput(
34 |
35 | )}
36 |
37 | {this.blurRequiredInput(
38 |
39 | )}
40 |
);
41 | }
42 | }
43 |
44 | Test = createForm({
45 | withRef: true,
46 | })(Test);
47 |
48 | let container;
49 | let component;
50 | let form;
51 |
52 | beforeEach(() => {
53 | container = document.createElement('div');
54 | document.body.appendChild(container);
55 | component = ReactDOM.render(, container);
56 | component = component.refs.wrappedComponent;
57 | form = component.props.form;
58 | });
59 |
60 | afterEach(() => {
61 | ReactDOM.unmountComponentAtNode(container);
62 | document.body.removeChild(container);
63 | });
64 |
65 | it('collect value', () => {
66 | expect(form.getFieldValue('normal')).toBe(undefined);
67 | Simulate.change(form.getFieldInstance('normal'));
68 | expect(form.getFieldValue('normal')).toBe('');
69 | form.getFieldInstance('normal').value = '1';
70 | Simulate.change(form.getFieldInstance('normal'));
71 | expect(form.getFieldValue('normal')).toBe('1');
72 | });
73 |
74 | it('validate value', () => {
75 | expect(form.getFieldValue('required')).toBe(undefined);
76 | Simulate.change(form.getFieldInstance('required'));
77 | expect(form.getFieldValue('required')).toBe('');
78 | expect(form.getFieldError('required')).toEqual(['required is required']);
79 | form.getFieldInstance('required').value = '1';
80 | Simulate.change(form.getFieldInstance('required'));
81 | expect(form.getFieldValue('required')).toBe('1');
82 | expect(form.getFieldError('required')).toBe(undefined);
83 | });
84 |
85 |
86 | it('validate trigger value', () => {
87 | expect(form.getFieldValue('blurRequired')).toBe(undefined);
88 | Simulate.change(form.getFieldInstance('blurRequired'));
89 | expect(form.getFieldValue('blurRequired')).toBe('');
90 | expect(form.getFieldError('blurRequired')).toBe(undefined);
91 | Simulate.blur(form.getFieldInstance('blurRequired'));
92 | expect(form.getFieldValue('blurRequired')).toBe('');
93 | expect(form.getFieldError('blurRequired')).toEqual(['blurRequired is required']);
94 | form.getFieldInstance('blurRequired').value = '1';
95 | Simulate.change(form.getFieldInstance('blurRequired'));
96 | Simulate.blur(form.getFieldInstance('blurRequired'));
97 | expect(form.getFieldValue('blurRequired')).toBe('1');
98 | expect(form.getFieldError('blurRequired')).toBe(undefined);
99 | });
100 |
101 | it('validateFields works for error', (callback) => {
102 | form.validateFields((errors, values) => {
103 | expect(Object.keys(errors).length).toBe(2);
104 | expect(errors.required.errors.map(e => e.message)).toEqual(['required is required']);
105 | expect(errors.blurRequired.errors.map(e => e.message)).toEqual(['blurRequired is required']);
106 | expect(values.normal).toBe(undefined);
107 | expect(values.blurRequired).toBe(undefined);
108 | expect(values.required).toBe(undefined);
109 | callback();
110 | });
111 | });
112 |
113 | it('validateFields works for ok', (callback) => {
114 | form.getFieldInstance('required').value = '2';
115 | form.getFieldInstance('blurRequired').value = '1';
116 | Simulate.change(form.getFieldInstance('blurRequired'));
117 | Simulate.change(form.getFieldInstance('required'));
118 | form.validateFields((errors, values) => {
119 | expect(errors).toBeFalsy();
120 | expect(values.normal).toBe(undefined);
121 | expect(values.blurRequired).toBe('1');
122 | expect(values.required).toBe('2');
123 | callback();
124 | });
125 | });
126 |
127 | it('resetFields works', () => {
128 | form.getFieldInstance('required').value = '1';
129 | Simulate.change(form.getFieldInstance('required'));
130 | expect(form.getFieldValue('required')).toBe('1');
131 | expect(form.getFieldError('required')).toBe(undefined);
132 | form.resetFields();
133 | expect(form.getFieldValue('required')).toBe(undefined);
134 | expect(form.getFieldError('required')).toBe(undefined);
135 | });
136 |
137 | it('setFieldsInitialValue works', () => {
138 | form.setFieldsInitialValue({
139 | normal: '4',
140 | });
141 | form.getFieldInstance('normal').value = '2';
142 | Simulate.change(form.getFieldInstance('normal'));
143 | expect(form.getFieldValue('normal')).toBe('2');
144 | form.resetFields();
145 | expect(form.getFieldValue('normal')).toBe('4');
146 | });
147 | });
148 |
149 | describe('dynamic', () => {
150 | it('change validateTrigger', () => {
151 | class Test extends React.Component {
152 | state = {
153 | inited: false,
154 | };
155 |
156 | render() {
157 | const { getFieldDecorator } = this.props.form;
158 | return (
159 |
160 | {getFieldDecorator('title', {
161 | validateTrigger: this.state.inited ? 'onChange' : 'onBlur',
162 | rules: [
163 | { required: true, message: 'Title is required' },
164 | { min: 3, message: 'Title should be 3+ characters' },
165 | ],
166 | })()}
167 |
168 | );
169 | }
170 | }
171 |
172 | Test = createForm({
173 | withRef: true,
174 | })(Test);
175 |
176 | const container = document.createElement('div');
177 | document.body.appendChild(container);
178 | const app = ReactDOM.render(, container);
179 | const component = app.refs.wrappedComponent;
180 | const form = component.props.form;
181 |
182 | Simulate.blur(form.getFieldInstance('title'));
183 | expect(form.getFieldError('title')).toEqual(['Title is required']);
184 |
185 | component.setState({ inited: true });
186 |
187 | form.getFieldInstance('title').value = '1';
188 | Simulate.change(form.getFieldInstance('title'));
189 | expect(form.getFieldValue('title')).toBe('1');
190 | expect(form.getFieldError('title')).toEqual(['Title should be 3+ characters']);
191 | });
192 | });
193 |
--------------------------------------------------------------------------------
/tests/getFieldProps.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 |
4 | import React from 'react';
5 | import { mount } from 'enzyme';
6 | import createForm from '../src/createForm';
7 |
8 | describe('fieldname', () => {
9 | // https://github.com/ant-design/ant-design/issues/8985
10 | it('support disordered array', () => {
11 | const Test = createForm({ withRef: true })(
12 | class extends React.Component {
13 | render() {
14 | const { getFieldProps } = this.props.form;
15 | return (
16 |
17 |
22 |
27 |
28 | );
29 | }
30 | }
31 | );
32 |
33 | const wrapper = mount();
34 | const form = wrapper.ref('wrappedComponent').props.form;
35 | expect(() => form.validateFields()).not.toThrow();
36 | });
37 | });
38 |
39 | describe('initialValue', () => {
40 | it('works', () => {
41 | const Test = createForm({ withRef: true })(
42 | class extends React.Component {
43 | render() {
44 | const { getFieldProps } = this.props.form;
45 | return ;
46 | }
47 | }
48 | );
49 | const wrapper = mount();
50 | const form = wrapper.ref('wrappedComponent').props.form;
51 | expect(form.getFieldValue('normal')).toBe('1');
52 | wrapper.find('input').simulate('change', { target: { value: '2' } });
53 | expect(form.getFieldValue('normal')).toBe('2');
54 | form.resetFields();
55 | expect(form.getFieldValue('normal')).toBe('1');
56 | });
57 | });
58 |
59 | describe('getValueProps', () => {
60 | it('works', () => {
61 | const Test = createForm({ withRef: true })(
62 | class extends React.Component {
63 | render() {
64 | const { getFieldProps } = this.props.form;
65 | return (
66 |
73 | );
74 | }
75 | }
76 | );
77 | const wrapper = mount();
78 | const form = wrapper.ref('wrappedComponent').props.form;
79 | wrapper.find('input').simulate('change', { target: { value: '2' } });
80 | expect(form.getFieldValue('normal')).toBe('2');
81 | expect(form.getFieldInstance('normal').value).toBe('21');
82 | });
83 | });
84 |
85 | describe('getValueFromEvent', () => {
86 | it('works', () => {
87 | const Test = createForm({ withRef: true })(
88 | class extends React.Component {
89 | render() {
90 | const { getFieldProps } = this.props.form;
91 | return (
92 |
99 | );
100 | }
101 | }
102 | );
103 | const wrapper = mount();
104 | const form = wrapper.ref('wrappedComponent').props.form;
105 | wrapper.find('input').simulate('change', { target: { value: '2' } });
106 | expect(form.getFieldValue('normal')).toBe('21');
107 | });
108 | });
109 |
110 | describe('normalize', () => {
111 | it('works', () => {
112 | const Test = createForm({ withRef: true })(
113 | class extends React.Component {
114 | toUpper = (v) => {
115 | return v && v.toUpperCase();
116 | }
117 | render() {
118 | const { getFieldProps } = this.props.form;
119 | return (
120 |
125 | );
126 | }
127 | }
128 | );
129 | const wrapper = mount();
130 | const form = wrapper.ref('wrappedComponent').props.form;
131 | wrapper.find('input').simulate('change', { target: { value: 'a' } });
132 | expect(form.getFieldValue('normal')).toBe('A');
133 | expect(form.getFieldInstance('normal').value).toBe('A');
134 | });
135 | });
136 |
137 | describe('validate', () => {
138 | it('works', () => {
139 | const Test = createForm({ withRef: true })(
140 | class extends React.Component {
141 | render() {
142 | const { getFieldProps } = this.props.form;
143 | return (
144 |
154 | );
155 | }
156 | }
157 | );
158 | const wrapper = mount();
159 | const form = wrapper.ref('wrappedComponent').props.form;
160 | expect(form.getFieldValue('normal')).toBe(undefined);
161 | wrapper.find('input').simulate('change', { target: { value: '' } });
162 | expect(form.getFieldValue('normal')).toBe('');
163 | expect(form.getFieldError('normal')).toBe(undefined);
164 | wrapper.find('input').simulate('blur', { target: { value: '' } });
165 | expect(form.getFieldValue('normal')).toBe('');
166 | expect(form.getFieldError('normal')).toEqual(['normal is required']);
167 | wrapper.find('input').simulate('blur', { target: { value: '1' } });
168 | expect(form.getFieldValue('normal')).toBe('1');
169 | expect(form.getFieldError('normal')).toBe(undefined);
170 | });
171 |
172 | it('suport jsx message', () => {
173 | const Test = createForm({ withRef: true })(
174 | class extends React.Component {
175 | render() {
176 | const { getFieldProps } = this.props.form;
177 | return (
178 | 1,
183 | }],
184 | })}
185 | />
186 | );
187 | }
188 | }
189 | );
190 | const wrapper = mount();
191 | const form = wrapper.ref('wrappedComponent').props.form;
192 | wrapper.find('input').simulate('change');
193 | expect(form.getFieldError('required').length).toBe(1);
194 | expect(form.getFieldError('required')[0].type).toBe('b');
195 | });
196 | });
197 |
198 | describe('hidden', () => {
199 | it('works', (callback) => {
200 | const Test = createForm({ withRef: true })(
201 | class extends React.Component {
202 | render() {
203 | const { getFieldProps } = this.props.form;
204 | return (
205 |
212 | );
213 | }
214 | }
215 | );
216 | const wrapper = mount();
217 | const form = wrapper.ref('wrappedComponent').props.form;
218 | expect(form.getFieldsValue()).toEqual({});
219 | form.validateFields((errors, values) => {
220 | expect(errors).toBe(null);
221 | expect(values).toEqual({});
222 | callback();
223 | });
224 | });
225 |
226 | it('can be set', () => {
227 | const Test = createForm({ withRef: true })(
228 | class extends React.Component {
229 | render() {
230 | const { getFieldProps } = this.props.form;
231 | return (
232 |
238 | );
239 | }
240 | }
241 | );
242 | const wrapper = mount();
243 | const form = wrapper.ref('wrappedComponent').props.form;
244 | expect(form.getFieldsValue(['normal'])).toEqual({ normal: '' });
245 | form.setFieldsValue({ normal: 'Hello world!' });
246 | expect(form.getFieldsValue(['normal'])).toEqual({ normal: 'Hello world!' });
247 | });
248 | });
249 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | const req = require.context('.', false, /\.spec\.js$/);
2 | req.keys().forEach(req);
3 |
--------------------------------------------------------------------------------
/tests/overview.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 |
4 | import React from 'react';
5 | import { mount } from 'enzyme';
6 | import createForm from '../src/createForm';
7 |
8 | describe('getFieldProps\' behaviors', () => {
9 | it('collect value and relative getters', () => {
10 | const Test = createForm({ withRef: true })(
11 | class extends React.Component {
12 | render() {
13 | const { getFieldProps } = this.props.form;
14 | return (
15 |
20 | );
21 | }
22 | }
23 | );
24 | const wrapper = mount();
25 | const form = wrapper.ref('wrappedComponent').props.form;
26 | wrapper.find('input').at(0).simulate('change', { target: { value: '1' } });
27 | expect(form.getFieldValue('normal')).toBe('1');
28 | wrapper.find('input').at(1).simulate('change', { target: { value: 'a' } });
29 | expect(form.getFieldValue('nested1.a[0]')).toBe('a');
30 | expect(form.getFieldValue('nested1')).toEqual({ a: ['a'] });
31 | wrapper.find('input').at(2).simulate('change', { target: { value: 'b' } });
32 | expect(form.getFieldValue('nested2[0].b')).toBe('b');
33 | expect(form.getFieldValue('nested2')).toEqual([{ b: 'b' }]);
34 |
35 | expect(form.getFieldsValue(['normal', 'nested1', 'nested2[0]']))
36 | .toEqual({
37 | normal: '1',
38 | nested1: { a: ['a'] },
39 | nested2: [{ b: 'b' }],
40 | });
41 | });
42 |
43 | it('validate value and relative getters', () => {
44 | const Test = createForm({ withRef: true })(
45 | class extends React.Component {
46 | render() {
47 | const { getFieldProps } = this.props.form;
48 | return (
49 |
54 | );
55 | }
56 | }
57 | );
58 | const wrapper = mount();
59 | const form = wrapper.ref('wrappedComponent').props.form;
60 |
61 | wrapper.find('input').at(0).simulate('change');
62 | expect(form.getFieldError('normal')).toEqual(['normal is required']);
63 | wrapper.find('input').at(0).simulate('change', { target: { value: '1' } });
64 | expect(form.getFieldError('normal')).toBe(undefined);
65 |
66 | wrapper.find('input').at(1).simulate('change');
67 | expect(form.getFieldError('nested1.a[0]')).toEqual(['nested1.a[0] is required']);
68 | expect(form.getFieldError('nested1')).toEqual({ a: [['nested1.a[0] is required']] });
69 | wrapper.find('input').at(1).simulate('change', { target: { value: '1' } });
70 | expect(form.getFieldError('nested1.a[0]')).toBe(undefined);
71 | expect(form.getFieldError('nested1')).toEqual({ a: [undefined] });
72 |
73 | wrapper.find('input').at(2).simulate('change');
74 | expect(form.getFieldError('nested2[0].b')).toEqual(['nested2[0].b is required']);
75 | expect(form.getFieldError('nested2')).toEqual([{ b: ['nested2[0].b is required'] }]);
76 | wrapper.find('input').at(2).simulate('change', { target: { value: '1' } });
77 | expect(form.getFieldError('nested2[0].b')).toBe(undefined);
78 | expect(form.getFieldError('nested2')).toEqual([{ b: undefined }]);
79 |
80 | expect(form.getFieldsError(['normal', 'nested1', 'nested2[0]']))
81 | .toEqual({
82 | normal: undefined,
83 | nested1: { a: [undefined] },
84 | nested2: [{ b: undefined }],
85 | });
86 | });
87 | });
88 |
89 | describe('createForm\'s form behavior', () => {
90 | it('getFieldValue should return `undefined` when `name` is not registered', () => {
91 | const Test = createForm({ withRef: true })(
92 | class extends React.Component {
93 | render() { return null; }
94 | }
95 | );
96 | const wrapper = mount();
97 | const form = wrapper.ref('wrappedComponent').props.form;
98 | expect(form.getFieldValue('not-registered')).toBe(undefined);
99 | });
100 |
101 | it('setFieldsInitialValue works', () => {
102 | const Test = createForm({ withRef: true })(
103 | class extends React.Component {
104 | render() {
105 | const { getFieldProps } = this.props.form;
106 | return (
107 |
112 | );
113 | }
114 | }
115 | );
116 | const wrapper = mount();
117 | const form = wrapper.ref('wrappedComponent').props.form;
118 | form.setFieldsInitialValue({
119 | normal: '1',
120 | nested1: { a: ['2'] },
121 | nested2: [{ b: '3' }],
122 | });
123 | expect(form.getFieldsValue()).toEqual({
124 | normal: '1',
125 | nested1: { a: ['2'] },
126 | nested2: [{ b: '3' }],
127 | });
128 | });
129 |
130 | it('resetFields works', () => {
131 | const Test = createForm({ withRef: true })(
132 | class extends React.Component {
133 | render() {
134 | const { getFieldProps } = this.props.form;
135 | return (
136 |
141 | );
142 | }
143 | }
144 | );
145 | const wrapper = mount();
146 | const form = wrapper.ref('wrappedComponent').props.form;
147 |
148 | wrapper.find('input').at(0).simulate('change', { target: { value: '' } });
149 | expect(form.getFieldValue('normal')).toBe('');
150 | expect(form.getFieldError('normal')).toEqual(['normal is required']);
151 | wrapper.find('input').at(1).simulate('change', { target: { value: '' } });
152 | expect(form.getFieldValue('nested1.a[0]')).toBe('');
153 | expect(form.getFieldError('nested1.a[0]')).toEqual(['nested1.a[0] is required']);
154 | wrapper.find('input').at(2).simulate('change', { target: { value: '' } });
155 | expect(form.getFieldValue('nested2[0].b')).toBe('');
156 | expect(form.getFieldError('nested2[0].b')).toEqual(['nested2[0].b is required']);
157 | form.resetFields(['normal', 'nested1', 'nested2[0]']);
158 | expect(form.getFieldValue('normal')).toBe(undefined);
159 | expect(form.getFieldError('normal')).toBe(undefined);
160 | expect(form.getFieldValue('nested1.a[0]')).toBe(undefined);
161 | expect(form.getFieldError('nested1.a[0]')).toBe(undefined);
162 | expect(form.getFieldValue('nested2[0].b')).toBe(undefined);
163 | expect(form.getFieldError('nested2[0].b')).toBe(undefined);
164 | });
165 |
166 | it('validateFields works for errors', (callback) => {
167 | const Test = createForm({ withRef: true })(
168 | class extends React.Component {
169 | render() {
170 | const { getFieldProps } = this.props.form;
171 | return ;
172 | }
173 | }
174 | );
175 | const wrapper = mount();
176 | const form = wrapper.ref('wrappedComponent').props.form;
177 | form.validateFields((errors, values) => {
178 | expect(errors).toEqual({
179 | normal: {
180 | errors: [{ field: 'normal', message: 'normal is required' }],
181 | },
182 | });
183 | expect(values).toEqual({ normal: undefined });
184 | callback();
185 | });
186 | });
187 |
188 | it('validateFields works for ok', (callback) => {
189 | const Test = createForm({ withRef: true })(
190 | class extends React.Component {
191 | render() {
192 | const { getFieldProps } = this.props.form;
193 | return ;
194 | }
195 | }
196 | );
197 | const wrapper = mount();
198 | const form = wrapper.ref('wrappedComponent').props.form;
199 | wrapper.find('input').simulate('change', { target: { value: '1' } });
200 | form.validateFields((errors, values) => {
201 | expect(errors).toBe(null);
202 | expect(values).toEqual({ normal: '1' });
203 | callback();
204 | });
205 | });
206 |
207 | it('validateFields(names, callback) works', (callback) => {
208 | const Test = createForm({ withRef: true })(
209 | class extends React.Component {
210 | render() {
211 | const { getFieldProps } = this.props.form;
212 | return (
213 |
218 | );
219 | }
220 | }
221 | );
222 | const wrapper = mount();
223 | const form = wrapper.ref('wrappedComponent').props.form;
224 | form.validateFields(['nested1', 'nested2[0]'], (errors, values) => {
225 | expect(errors).toEqual({
226 | nested1: {
227 | a: [{
228 | errors: [{ field: 'nested1.a[0]', message: 'nested1.a[0] is required' }],
229 | }],
230 | },
231 | nested2: [{
232 | b: {
233 | errors: [{ field: 'nested2[0].b', message: 'nested2[0].b is required' }],
234 | },
235 | }],
236 | });
237 | expect(values).toEqual({
238 | nested1: { a: [undefined] },
239 | nested2: [{ b: undefined }],
240 | });
241 | callback();
242 | });
243 | });
244 | });
245 |
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) {
2 | return setTimeout(cb, 0);
3 | };
4 |
5 | const Enzyme = require('enzyme');
6 | const Adapter = require('enzyme-adapter-react-16');
7 |
8 | Enzyme.configure({ adapter: new Adapter() });
9 |
--------------------------------------------------------------------------------
/tests/switch-field.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp,
2 | react/prefer-stateless-function */
3 | import React from 'react';
4 | import { mount } from 'enzyme';
5 | import createDOMForm from '../src/createDOMForm';
6 |
7 | jest.mock('dom-scroll-into-view', () => jest.fn());
8 |
9 | // https://github.com/ant-design/ant-design/issues/12560
10 | describe('switch field should not lost field', () => {
11 |
12 | // assume we have fields: [a, b, c] when switch a with b
13 | // mark at node a: a as toRemove, b as toAdd
14 | // mark at node b: b as toRemove, a as toAdd
15 | // if do change instantly, result will be
16 | // [a, b, c] -> [b, c] -> [a, c], it's a wrong result
17 | // but collect them and do this after, result will be
18 | // [a, b, c] -> (remove a,b) [c] -> (add a,b) [a,b, c]
19 |
20 | // Prepare
21 |
22 | class Demo extends React.Component {
23 | state = {
24 | list: ['a', 'b', 'c'],
25 | };
26 | switch = () => {
27 | this.setState({
28 | list: ['b', 'a', 'c'],
29 | });
30 | };
31 | render() {
32 | const { getFieldDecorator } = this.props.form;
33 | const [one, two, three] = this.state.list;
34 | // do not use map
35 | // react will detect it by key and knowing there was a change on order.
36 | // we need to test [custom reparenting], so use hard written three element.
37 | return
38 | {getFieldDecorator(one)()}
39 | {getFieldDecorator(two)()}
40 | {getFieldDecorator(three)()}
41 |
42 |
43 | }
44 | }
45 |
46 | const FormDemo = createDOMForm({
47 | withRef: true,
48 | })(Demo);
49 |
50 | // Do the test
51 | it('Preserve right fields when switch them', () => {
52 | const wrapper = mount(, { attachTo: document.body });
53 | const form = wrapper.find('Demo').props().form;
54 |
55 | wrapper.find('.one').simulate('change', {target: {value: 'value1'}});
56 |
57 | expect(Object.keys(form.getFieldsValue()))
58 | .toEqual(expect.arrayContaining(["a", "b", "c"]));
59 | expect(form.getFieldValue('a')).toBe('value1');
60 | expect(wrapper.find('.one').getDOMNode().value).toBe('value1');
61 |
62 | wrapper.find('.sw').simulate('click');
63 |
64 | expect(Object.keys(form.getFieldsValue()))
65 | .toEqual(expect.arrayContaining(["a", "b", "c"]));
66 | expect(form.getFieldValue('a')).toBe('value1');
67 | expect(wrapper.find('.two').getDOMNode().value).toBe('value1');
68 | });
69 | });
--------------------------------------------------------------------------------
/tests/utils.spec.js:
--------------------------------------------------------------------------------
1 | import createFormField, { isFormField } from '../src/createFormField';
2 | import { flattenFields } from '../src/utils';
3 |
4 | describe('utils.flattenFields', () => {
5 | it('works', () => {
6 | const fields = {
7 | user: {
8 | name: createFormField({
9 | value: 'benjycui',
10 | }),
11 | age: createFormField({
12 | value: 18,
13 | }),
14 | hobbies: [
15 | createFormField({
16 | value: 'Archery',
17 | }),
18 | createFormField({
19 | value: 'Roller Skating',
20 | }),
21 | ],
22 | },
23 | };
24 |
25 | expect(flattenFields(fields, (_, node) => isFormField(node)))
26 | .toEqual({
27 | 'user.name': { value: 'benjycui' },
28 | 'user.age': { value: 18 },
29 | 'user.hobbies[0]': { value: 'Archery' },
30 | 'user.hobbies[1]': { value: 'Roller Skating' },
31 | });
32 | });
33 |
34 | it('just ignore `undefined` when `undefined` is not a valid leaf node', () => {
35 | const fields = {
36 | user: {
37 | name: undefined,
38 | age: undefined,
39 | hobbies: [
40 | undefined,
41 | createFormField({
42 | value: 'Roller Skating',
43 | }),
44 | ],
45 | },
46 | };
47 |
48 | expect(flattenFields(fields, (_, node) => isFormField(node)))
49 | .toEqual({
50 | 'user.hobbies[1]': { value: 'Roller Skating' },
51 | });
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/tests/validateArray.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-multi-comp, react/prefer-stateless-function, react/no-render-return-value */
2 |
3 | import React from "react";
4 | import ReactDOM from "react-dom";
5 | import createForm from "../src/createForm";
6 |
7 | class MyInput extends React.Component {
8 | onChange = ({ target: { value } }) => {
9 | const { onChange } = this.props;
10 | onChange(value.split(","));
11 | };
12 | render() {
13 | const { value = [] } = this.props;
14 | return (
15 |
16 | );
17 | }
18 | }
19 |
20 | class Test extends React.Component {
21 | render() {
22 | const { getFieldDecorator } = this.props.form;
23 | return (
24 |
25 | {getFieldDecorator("url_array", {
26 | initialValue: ["test"],
27 | rules: [
28 | {
29 | required: true,
30 | message: "The tags must be urls",
31 | type: "array",
32 | defaultField: { type: "url" }
33 | }
34 | ]
35 | })()}
36 | {getFieldDecorator("url_array1.123", {
37 | rules: [{ required: true, message: "Additional test" }]
38 | })()}
39 | {getFieldDecorator("url_array-1", {
40 | rules: [{ required: true, message: "Additional test 2" }]
41 | })()}
42 |
43 | );
44 | }
45 | }
46 |
47 | Test = createForm({
48 | withRef: true
49 | })(Test);
50 |
51 | describe("validate array type", () => {
52 | let container;
53 | let component;
54 | let form;
55 |
56 | beforeEach(() => {
57 | container = document.createElement("div");
58 | document.body.appendChild(container);
59 | component = ReactDOM.render(, container);
60 | component = component.refs.wrappedComponent;
61 | form = component.props.form;
62 | });
63 |
64 | afterEach(() => {
65 | ReactDOM.unmountComponentAtNode(container);
66 | document.body.removeChild(container);
67 | });
68 |
69 | it("forceValidate works", done => {
70 | form.validateFields(errors => {
71 | expect(errors).toBeTruthy();
72 | expect(errors.url_array.errors).toEqual([
73 | {
74 | field: "url_array.0",
75 | message: "url_array.0 is not a valid url"
76 | }
77 | ]);
78 | expect(errors.url_array1["123"].errors).toEqual([
79 | { message: "Additional test", field: "url_array1.123" }
80 | ]);
81 | expect(errors["url_array-1"].errors).toEqual([
82 | { message: "Additional test 2", field: "url_array-1" }
83 | ]);
84 | done();
85 | });
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/tests/validateFields.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef, react/prop-types, react/no-render-return-value */
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { Simulate } from 'react-dom/test-utils';
6 | import createForm from '../src/createForm';
7 |
8 | class Test extends React.Component {
9 | force = (rule, value, callback) => {
10 | this.props.form.validateFields(['check'], {
11 | force: true,
12 | }, (errors) => {
13 | if (errors) {
14 | const es = Object.keys(errors).map((name) => {
15 | return errors[name].errors;
16 | }).reduce((result, current) => {
17 | return result.concat(...current.map(entity => ({
18 | ...entity, message: `${entity.message}_force`,
19 | })));
20 | }, []);
21 | callback(es);
22 | } else {
23 | callback();
24 | }
25 | });
26 | }
27 |
28 | check = (rule, value, callback) => {
29 | if ((value || '').length < (this.props.form.getFieldValue('force') || '').length) {
30 | callback('error');
31 | } else {
32 | callback();
33 | }
34 | }
35 |
36 | render() {
37 | const { getFieldProps } = this.props.form;
38 | return (
39 |
40 |
41 |
45 |
50 |
51 | );
52 | }
53 | }
54 |
55 | Test = createForm({
56 | withRef: true,
57 | })(Test);
58 |
59 | describe('validateFields', () => {
60 | let container;
61 | let component;
62 | let form;
63 |
64 | beforeEach(() => {
65 | container = document.createElement('div');
66 | document.body.appendChild(container);
67 | component = ReactDOM.render(, container);
68 | component = component.refs.wrappedComponent;
69 | form = component.props.form;
70 | });
71 |
72 | afterEach(() => {
73 | ReactDOM.unmountComponentAtNode(container);
74 | document.body.removeChild(container);
75 | });
76 |
77 | it('forceValidate works', (done) => {
78 | form.getFieldInstance('check').value = '1';
79 | Simulate.change(form.getFieldInstance('check'));
80 | expect(form.getFieldError('check')).toBeFalsy();
81 | form.getFieldInstance('force').value = '11';
82 | Simulate.change(form.getFieldInstance('force'));
83 | expect(form.getFieldError('check')).toBeFalsy();
84 | form.validateFields((errors) => {
85 | expect(errors).toBeTruthy();
86 | expect(Object.keys(errors).length).toBe(1);
87 | expect(form.getFieldError('check')).toEqual(['error', 'error_force']);
88 | expect(errors.check.errors.map(e => e.message)).toEqual(['error', 'error_force']);
89 | expect(form.getFieldError('force')).toBeFalsy();
90 | done();
91 | });
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/tests/validateFieldsAndScroll.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types, react/no-render-return-value */
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import { Simulate } from 'react-dom/test-utils'
5 | import createDOMForm from '../src/createDOMForm'
6 |
7 | class Test extends React.Component {
8 | check = (rule, value, callback) => {
9 | setTimeout(() => {
10 | if (value === '1') {
11 | callback()
12 | } else {
13 | callback(new Error('must be 1'))
14 | }
15 | }, 100)
16 | }
17 |
18 | render() {
19 | const { getFieldProps } = this.props.form
20 | return (
21 |
22 |
23 |
28 |
29 | )
30 | }
31 | }
32 |
33 | Test = createDOMForm({
34 | withRef: true,
35 | })(Test)
36 |
37 | describe('Test validateFieldsAndScroll', () => {
38 | let container
39 | let component
40 | let form
41 |
42 | beforeEach(() => {
43 | container = document.createElement('div')
44 | document.body.appendChild(container)
45 | component = ReactDOM.render(, container)
46 | component = component.refs.wrappedComponent
47 | form = component.props.form
48 | })
49 |
50 | afterEach(() => {
51 | ReactDOM.unmountComponentAtNode(container)
52 | document.body.removeChild(container)
53 | })
54 |
55 | it('validate validateFieldsAndScroll promise', (done) => {
56 | form.getFieldInstance('async').value = '1'
57 | Simulate.change(form.getFieldInstance('async'))
58 | return form.validateFieldsAndScroll().then(values => {
59 | expect(values.normal).toBe(undefined)
60 | expect(values.async).toBe('1')
61 | done()
62 | })
63 | })
64 | })
65 |
--------------------------------------------------------------------------------