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

Commit f59ddc1

Browse files
committed
Merge branch 'sl33kr-form_field_validation'
2 parents 750cb5a + 3599862 commit f59ddc1

File tree

6 files changed

+74
-39
lines changed

6 files changed

+74
-39
lines changed

src/actions/field-actions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ function createFieldActions(s = defaultStrategies) {
8282
? actionTypes.SET_ERRORS
8383
: actionTypes.SET_VALIDITY,
8484
model,
85+
...options,
8586
[options.errors ? 'errors' : 'validity']: validity,
8687
});
8788

src/components/control-component.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ function getReadOnlyValue(props) {
4747
}
4848
}
4949

50+
function mergeOrSetErrors(model, errors) {
51+
return actions.setErrors(model, errors, {
52+
merge: isPlainObject(errors),
53+
});
54+
}
55+
5056
const propTypes = {
5157
model: PropTypes.oneOfType([
5258
PropTypes.func,
@@ -259,10 +265,10 @@ function createControlClass(customControlPropsMap = {}, s = defaultStrategy) {
259265
: fieldErrors;
260266

261267
if (!fieldValue || !shallowEqual(mergedErrors, fieldValue.errors)) {
262-
return actions.setErrors(model, mergedErrors);
268+
return mergeOrSetErrors(model, mergedErrors);
263269
}
264270
} else if (nodeErrors && Object.keys(nodeErrors).length) {
265-
return actions.setErrors(model, nodeErrors);
271+
return mergeOrSetErrors(model, nodeErrors);
266272
}
267273

268274
return false;
@@ -566,7 +572,7 @@ function createControlClass(customControlPropsMap = {}, s = defaultStrategy) {
566572
: fieldErrors;
567573

568574
if (!shallowEqual(errors, fieldValue.errors)) {
569-
dispatch(actions.setErrors(model, errors));
575+
dispatch(mergeOrSetErrors(model, errors));
570576
}
571577

572578
return modelValue;

src/reducers/form-actions-reducer.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +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';
1112
import isValid, { fieldsValid } from '../form/is-valid';
1213
import isValidityValid from '../utils/is-validity-valid';
1314
import isValidityInvalid from '../utils/is-validity-invalid';
@@ -157,7 +158,16 @@ export default function formActionsReducer(state, action, localPath) {
157158
case actionTypes.SET_VALIDITY:
158159
case actionTypes.SET_ERRORS: {
159160
const isErrors = action.type === actionTypes.SET_ERRORS;
160-
const validity = isErrors ? action.errors : action.validity;
161+
let validity;
162+
if (isErrors) {
163+
validity = action.merge
164+
? merge({ ...fieldState.errors }, action.errors)
165+
: action.errors;
166+
} else {
167+
validity = action.merge
168+
? merge({ ...fieldState.validity }, action.validity)
169+
: action.validity;
170+
}
161171

162172
const inverseValidity = isPlainObject(validity)
163173
? mapValues(validity, inverse)

src/utils/merge.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
1-
function isObject(item) {
2-
return (item && typeof item === 'object' && !Array.isArray(item) && item !== null);
3-
}
1+
import i from 'icepick';
42

53
export default function mergeDeep(target, source) {
6-
if (isObject(target) && isObject(source)) {
7-
Object.keys(source).forEach(key => {
8-
if (isObject(source[key])) {
9-
if (!target[key]) Object.assign(target, { [key]: {} });
10-
mergeDeep(target[key], source[key]);
11-
} else {
12-
Object.assign(target, { [key]: source[key] });
13-
}
14-
});
15-
}
16-
17-
return target;
4+
return i.merge(target, source);
185
}

test/errors-component-spec.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,19 @@ Object.keys(testContexts).forEach((testKey) => {
294294
const form = TestUtils.renderIntoDocument(
295295
<Provider store={store}>
296296
<form>
297-
<Errors model="test.foo"
298-
messages={{
299-
length: (val, {length}) => `${val && val.length} chars is too short (must be at least ${length} chars)`,
300-
doNotShow: () => false,
301-
}}
297+
<Errors
298+
model="test.foo"
299+
messages={{
300+
length: (val, {length}) => `${val && val.length} chars is too short (must be at least ${length} chars)`,
301+
doNotShow: () => false,
302+
}}
302303
/>
303-
<Field model="test.foo"
304-
errors={{
305-
length: (v) => v && v.length && v.length > 5 ? false : {length: 5},
306-
doNotShow: () => false,
307-
}}
304+
<Field
305+
model="test.foo"
306+
errors={{
307+
length: (v) => v && v.length && v.length > 5 ? false : {length: 5},
308+
doNotShow: () => false,
309+
}}
308310
>
309311
<input type="text"/>
310312
</Field>

test/form-component-spec.js

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,7 @@ Object.keys(testContexts).forEach((testKey) => {
17721772
{ name: 'one' },
17731773
{ name: 'two' },
17741774
{ name: 'three' },
1775+
{ name: 'four' },
17751776
],
17761777
});
17771778
const store = testCreateStore({
@@ -1786,38 +1787,51 @@ Object.keys(testContexts).forEach((testKey) => {
17861787
validators={(model) => {
17871788
const field1 = 'items[0].name';
17881789
const field2 = 'items[1].name';
1790+
const field4 = 'items[3].name';
17891791
const hasValue = (value) => value && value.length;
17901792

17911793
const field1Value = get(model, field1);
17921794
const field2Value = get(model, field2);
1795+
const field4Value = get(model, field4);
17931796

17941797
const notRequired = () => Boolean(hasValue(field1Value) || hasValue(field2Value));
17951798
const containsOne = field1Value.includes('one');
17961799
const validations = {};
17971800

17981801
validations[field1] = {
17991802
required: notRequired(),
1800-
needsFoo: containsOne,
1803+
needsOne: containsOne,
18011804
};
18021805
validations[field2] = {
18031806
required: notRequired(),
18041807
};
1808+
validations[field4] = {
1809+
required: hasValue(field4Value),
1810+
};
18051811
return validations;
18061812
}}
18071813
errors={{
1808-
'items[2].name': (value) => { // eslint-disable-line arrow-body-style
1809-
return value.includes('three') ? false : { invalidThree: 'invalid three' };
1810-
},
1814+
'items[2].name': (value) => (
1815+
value.includes('three')
1816+
? false
1817+
: { invalidThree: 'invalid three' }
1818+
),
18111819
}}
18121820
>
1813-
{get(initialState, 'items').map((item, i) =>
1814-
<Control model={`test.items[${i}].name`} />
1815-
)}
1821+
<Control model="test.items[0].name" />
1822+
<Control model="test.items[1].name" />
1823+
<Control model="test.items[2].name" />
1824+
<Control
1825+
model="test.items[3].name"
1826+
validators={{
1827+
needsFour: (val) => val.includes('four'),
1828+
}}
1829+
/>
18161830
</Form>
18171831
</Provider>
18181832
);
18191833

1820-
const [input1, input2, input3] = TestUtils
1834+
const [input1, input2, input3, input4] = TestUtils
18211835
.scryRenderedDOMComponentsWithTag(form, 'input');
18221836

18231837
it('should initially validate each item', () => {
@@ -1870,10 +1884,10 @@ Object.keys(testContexts).forEach((testKey) => {
18701884
const { $form, items } = store.getState().testForm;
18711885

18721886
assert.isFalse(items[0].name.validity.required);
1873-
assert.isFalse(items[0].name.validity.needsFoo);
1887+
assert.isFalse(items[0].name.validity.needsOne);
18741888

18751889
assert.isTrue(items[0].name.errors.required);
1876-
assert.isTrue(items[0].name.errors.needsFoo);
1890+
assert.isTrue(items[0].name.errors.needsOne);
18771891

18781892
assert.isFalse(items[1].name.validity.required);
18791893
assert.isTrue(items[1].name.errors.required);
@@ -1883,6 +1897,21 @@ Object.keys(testContexts).forEach((testKey) => {
18831897

18841898
assert.isFalse($form.valid);
18851899
});
1900+
1901+
it('should aggregate form validations and field validations.', () => {
1902+
input4.value = '';
1903+
TestUtils.Simulate.change(input4);
1904+
1905+
const { $form, items } = store.getState().testForm;
1906+
1907+
assert.isFalse(items[3].name.validity.required);
1908+
assert.isFalse(items[3].name.validity.needsFour);
1909+
1910+
assert.isTrue(items[3].name.errors.required);
1911+
assert.isTrue(items[3].name.errors.needsFour);
1912+
1913+
assert.isFalse($form.valid);
1914+
});
18861915
});
18871916

18881917
describe('submit valid form no validators', () => {

0 commit comments

Comments
 (0)