Skip to content
This repository was archived by the owner on Aug 23, 2022. It is now read-only.

Commit b465ee4

Browse files
committed
Refactoring validation in form-component
1 parent 0b8a022 commit b465ee4

File tree

5 files changed

+86
-109
lines changed

5 files changed

+86
-109
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ stats.json
4343

4444
# Examples
4545
examples/sandbox
46+
47+
# Other
48+
.vscode/

src/components/form-component.js

Lines changed: 69 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ import React, { Component, PropTypes } from 'react';
22
import { connect } from 'react-redux';
33
import shallowEqual from '../utils/shallow-equal';
44
import _get from '../utils/get';
5-
import mapValues from '../utils/map-values';
65
import omit from '../utils/omit';
76

87
import actions from '../actions';
98
import getValidity from '../utils/get-validity';
10-
import invertValidity from '../utils/invert-validity';
119
import invertValidators from '../utils/invert-validators';
1210
import isValidityInvalid from '../utils/is-validity-invalid';
1311
import isValid from '../form/is-valid';
@@ -16,6 +14,7 @@ import getModel from '../utils/get-model';
1614
import getField from '../utils/get-field';
1715
import deepCompareChildren from '../utils/deep-compare-children';
1816
import containsEvent from '../utils/contains-event';
17+
import mergeValidity from '../utils/merge-validity';
1918
import invariant from 'invariant';
2019

2120
const propTypes = {
@@ -134,7 +133,7 @@ function createFormClass(s = defaultStrategy) {
134133
if (this.props.getRef) this.props.getRef(node);
135134
}
136135

137-
validate(nextProps, initial = false) {
136+
validate(nextProps, initial = false, submit = false) {
138137
const {
139138
model,
140139
dispatch,
@@ -162,12 +161,31 @@ function createFormClass(s = defaultStrategy) {
162161

163162
const validatorsChanged = validators !== this.props.validators
164163
|| errors !== this.props.errors;
164+
const fieldKeys = (validators ? Object.keys(validators) : [])
165+
.concat(errors ? Object.keys(errors) : []);
165166

166167
const fieldsErrors = {};
167168
let validityChanged = false;
168169

169-
// this is (internally) mutative for performance reasons.
170-
const validateField = (errorValidator, field) => {
170+
const keysToValidate = [];
171+
172+
fieldKeys.forEach(key => {
173+
if (!!~keysToValidate.indexOf(key)) return;
174+
175+
const valuesChanged = key === ''
176+
? modelValue !== nextProps.modelValue
177+
: (s.get(modelValue, key) !== s.get(nextProps.modelValue, key));
178+
179+
if (submit || initial
180+
|| valuesChanged
181+
|| (validators && (this.props.validators[key] !== validators[key]))
182+
|| (errors && (this.props.errors[key] !== errors[key]))
183+
|| !!~key.indexOf('[]')) {
184+
keysToValidate.push(key);
185+
}
186+
});
187+
188+
const validateField = (field, errorValidator) => {
171189
if (!!~field.indexOf('[]')) {
172190
const [parentModel, childModel] = field.split('[]');
173191

@@ -176,84 +194,82 @@ function createFormClass(s = defaultStrategy) {
176194
: nextProps.modelValue;
177195

178196
nextValue.forEach((subValue, index) => {
179-
validateField(errorValidator, `${parentModel}[${index}]${childModel}`);
197+
validateField(`${parentModel}[${index}]${childModel}`, errorValidator);
180198
});
181199
} else {
182200
const nextValue = field
183201
? s.get(nextProps.modelValue, field)
184202
: nextProps.modelValue;
185203

186-
const currentValue = field
187-
? s.get(modelValue, field)
188-
: modelValue;
189-
190204
const currentErrors = getField(formValue, field).errors;
205+
const fieldErrors = getValidity(errorValidator, nextValue);
191206

192-
// If the validators didn't change, the validity didn't change.
193-
if ((!initial && !validatorsChanged) && (nextValue === currentValue)) {
194-
fieldsErrors[field] = getField(formValue, field).errors;
195-
} else {
196-
const fieldErrors = getValidity(errorValidator, nextValue);
197-
198-
if (!validityChanged && !shallowEqual(fieldErrors, currentErrors)) {
199-
validityChanged = true;
200-
}
201-
202-
fieldsErrors[field] = fieldErrors;
207+
if (!validityChanged && !shallowEqual(fieldErrors, currentErrors)) {
208+
validityChanged = true;
203209
}
210+
211+
fieldsErrors[field] = mergeValidity(fieldsErrors[field], fieldErrors);
204212
}
205213
};
206214

207-
// Run errors first, validations should take precendence.
208-
// When run below will replace the contents of the fieldErrors[].
209-
mapValues(errors, validateField);
215+
keysToValidate.forEach(field => {
216+
if (validators && validators[field]) {
217+
validateField(field, invertValidators(validators[field]));
218+
}
219+
if (errors && errors[field]) {
220+
validateField(field, errors[field]);
221+
}
222+
});
210223

211224
if (typeof validators === 'function') {
212225
const nextValue = nextProps.modelValue;
213226
const currentValue = modelValue;
214227

215-
// If the validators didn't change, the validity didn't change.
216-
if ((!initial && !validatorsChanged) && (nextValue === currentValue)) {
217-
// TODO this will only set the errors on form when using the function.
218-
// How handle? Safe to assume will be no dispatch?
219-
// fieldsErrors[field] = getField(formValue, field).errors;
220-
} else {
221-
const multiFieldErrors = getValidity(validators, nextValue);
222-
223-
if (multiFieldErrors) {
224-
Object.keys(multiFieldErrors).forEach((key) => {
225-
// key will be the model value to apply errors to.
226-
const fieldErrors = multiFieldErrors[key];
227-
const currentErrors = getField(formValue, key).errors;
228+
if (!submit && (!initial && !validatorsChanged) && (nextValue === currentValue)) {
229+
// If neither the validators nor the values have changed,
230+
// the validity didn't change.
231+
return;
232+
}
228233

229-
// Invert validators
230-
Object.keys(fieldErrors).forEach((validationName) => {
231-
fieldErrors[validationName] = !fieldErrors[validationName];
232-
});
234+
const multiFieldErrors = getValidity(validators, nextValue);
233235

234-
if (!validityChanged && !shallowEqual(fieldErrors, currentErrors)) {
235-
validityChanged = true;
236-
}
236+
if (multiFieldErrors) {
237+
Object.keys(multiFieldErrors).forEach((key) => {
238+
// key will be the model value to apply errors to.
239+
const fieldErrors = multiFieldErrors[key];
240+
const currentErrors = getField(formValue, key).errors;
237241

238-
fieldsErrors[key] = fieldErrors;
242+
// Invert validators
243+
Object.keys(fieldErrors).forEach((validationName) => {
244+
fieldErrors[validationName] = !fieldErrors[validationName];
239245
});
240-
}
241-
}
242-
} else if (validators) {
243-
const errorValidators = invertValidators(validators);
244246

245-
mapValues(errorValidators, validateField);
247+
if (!validityChanged && !shallowEqual(fieldErrors, currentErrors)) {
248+
validityChanged = true;
249+
}
250+
251+
fieldsErrors[key] = mergeValidity(fieldsErrors[key], fieldErrors);
252+
});
253+
}
246254
}
247255

248256
// Compute form-level validity
249-
if (!fieldsErrors.hasOwnProperty('')) {
257+
if (!fieldsErrors.hasOwnProperty('') && !~fieldKeys.indexOf('')) {
250258
fieldsErrors[''] = false;
251259
validityChanged = validityChanged
252260
|| isValidityInvalid(formValue.$form.errors);
253261
}
254262

255263
if (validityChanged) {
256-
dispatch(s.actions.setFieldsErrors(model, fieldsErrors));
264+
dispatch(s.actions.setFieldsErrors(
265+
model,
266+
fieldsErrors,
267+
{ merge: submit }
268+
));
269+
}
270+
271+
if (submit) {
272+
dispatch(s.actions.addIntent(model, { type: 'submit' }));
257273
}
258274
}
259275

@@ -317,13 +333,10 @@ function createFormClass(s = defaultStrategy) {
317333
if (e && !this.props.action) e.preventDefault();
318334

319335
const {
320-
model,
321336
modelValue,
322337
formValue,
323338
onSubmit,
324-
dispatch,
325339
validators,
326-
errors: errorValidators,
327340
} = this.props;
328341

329342
const formValid = formValue
@@ -336,49 +349,7 @@ function createFormClass(s = defaultStrategy) {
336349
return modelValue;
337350
}
338351

339-
let fieldsValidity = {};
340-
341-
// this is (internally) mutative for performance reasons.
342-
const validateField = (validator, field) => {
343-
if (!!~field.indexOf('[]')) {
344-
const [parentModel, childModel] = field.split('[]');
345-
346-
const fieldValue = parentModel
347-
? s.get(modelValue, parentModel)
348-
: modelValue;
349-
350-
fieldValue.forEach((subValue, index) => {
351-
validateField(validator, `${parentModel}[${index}]${childModel}`);
352-
});
353-
} else {
354-
const fieldValue = field
355-
? s.get(modelValue, field)
356-
: modelValue;
357-
358-
const fieldValidity = getValidity(validator, fieldValue);
359-
360-
fieldsValidity[field] = fieldValidity;
361-
}
362-
};
363-
364-
if (typeof validators === 'function') {
365-
Object.assign(fieldsValidity, validators(modelValue));
366-
} else {
367-
mapValues(validators, validateField);
368-
}
369-
370-
fieldsValidity = invertValidity(fieldsValidity);
371-
372-
mapValues(errorValidators, validateField);
373-
374-
dispatch(s.actions.batch(model, [
375-
s.actions.setFieldsErrors(
376-
model,
377-
fieldsValidity,
378-
{ merge: true }
379-
),
380-
s.actions.addIntent(model, { type: 'submit' }),
381-
]));
352+
this.validate(this.props, false, true);
382353

383354
return modelValue;
384355
}

src/reducers/form-actions-reducer.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import map from '../utils/map';
88
import isPlainObject from '../utils/is-plain-object';
99
import mapValues from '../utils/map-values';
1010
import inverse from '../utils/inverse';
11-
import merge from '../utils/merge';
11+
import mergeValidity from '../utils/merge-validity';
1212
import isValid, { fieldsValid } from '../form/is-valid';
1313
import isValidityValid from '../utils/is-validity-valid';
1414
import isValidityInvalid from '../utils/is-validity-invalid';
@@ -18,15 +18,6 @@ import initialFieldState from '../constants/initial-field-state';
1818
import i from 'icepick';
1919
import { fieldOrForm, getMeta } from '../utils/create-field';
2020

21-
const mergeValidity = (fieldValidity, actionValidity) => {
22-
if (!isPlainObject(fieldValidity) || !isPlainObject(actionValidity)) {
23-
// can't merge string/boolean validity with keyed validity
24-
return actionValidity;
25-
}
26-
27-
return merge({ ...fieldValidity }, actionValidity);
28-
};
29-
3021
const resetFieldState = (field) => {
3122
if (!isPlainObject(field)) return field;
3223

src/utils/merge-validity.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import isPlainObject from './is-plain-object';
2+
import merge from './merge';
3+
4+
export default function mergeValidity(fieldValidity, actionValidity) {
5+
if (!isPlainObject(fieldValidity) || !isPlainObject(actionValidity)) {
6+
// can't merge string/boolean validity with keyed validity
7+
return actionValidity;
8+
}
9+
10+
return merge({ ...fieldValidity }, actionValidity);
11+
}

test/form-component-spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2129,7 +2129,8 @@ Object.keys(testContexts).forEach((testKey) => {
21292129
>
21302130
<Field
21312131
model=".name"
2132-
validators={{ fieldValidation: (value) => value && value.length }} validateOn="blur"
2132+
validators={{ fieldValidation: (value) => value && value.length }}
2133+
validateOn="blur"
21332134
>
21342135
<input type="text" />
21352136
</Field>

0 commit comments

Comments
 (0)