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

Commit 8d95837

Browse files
committed
Enhancement - validation now occurs on external changes. Fixes #183
1 parent 788313b commit 8d95837

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

src/components/control-component.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Component, createElement, PropTypes } from 'react';
22
import connect from 'react-redux/lib/components/connect';
3-
43
import _get from 'lodash/get';
4+
import merge from 'lodash/merge';
5+
6+
import { invertValidity, getFieldFromState, getValidity } from '../utils';
57
import { sequenceEventActions } from '../utils/sequence';
68
import actions from '../actions';
79

@@ -10,14 +12,19 @@ function mapStateToProps(state, props) {
1012
const modelString = typeof model === 'function'
1113
? model(state)
1214
: model;
15+
const fieldValue = getFieldFromState(state, modelString);
1316

1417
if (!mapProps) {
15-
return props;
18+
return {
19+
...props,
20+
fieldValue,
21+
};
1622
}
1723

1824
return mapProps({
1925
model,
2026
modelValue: _get(state, modelString),
27+
fieldValue,
2128
...props,
2229
...controlProps,
2330
...sequenceEventActions(props),
@@ -43,6 +50,22 @@ class Control extends Component {
4350
}
4451
}
4552

53+
componentDidUpdate(prevProps) {
54+
const {
55+
modelValue,
56+
fieldValue,
57+
validateOn,
58+
} = this.props;
59+
60+
if (fieldValue
61+
&& !fieldValue.validated
62+
&& modelValue !== prevProps.modelValue
63+
&& validateOn === 'change'
64+
) {
65+
this.validate();
66+
}
67+
}
68+
4669
handleKeyPress(event) {
4770
const { onSubmit } = this.props;
4871

@@ -51,6 +74,27 @@ class Control extends Component {
5174
}
5275
}
5376

77+
validate() {
78+
const {
79+
model,
80+
modelValue,
81+
validators,
82+
errors: errorValidators,
83+
dispatch,
84+
} = this.props;
85+
86+
const fieldValidity = getValidity(validators, modelValue);
87+
const fieldErrors = getValidity(errorValidators, modelValue);
88+
89+
const errors = validators
90+
? merge(invertValidity(fieldValidity), fieldErrors)
91+
: fieldErrors;
92+
93+
dispatch(actions.setErrors(model, errors));
94+
95+
return modelValue;
96+
}
97+
5498
render() {
5599
const { controlProps, component } = this.props;
56100

@@ -65,15 +109,30 @@ class Control extends Component {
65109
}
66110

67111
Control.propTypes = {
112+
model: PropTypes.oneOfType([
113+
PropTypes.func,
114+
PropTypes.string,
115+
]),
68116
control: PropTypes.any,
69117
onLoad: PropTypes.func,
70118
onSubmit: PropTypes.func,
71119
modelValue: PropTypes.any,
120+
fieldValue: PropTypes.object,
72121
mapProps: PropTypes.func,
73122
changeAction: PropTypes.func,
74123
updateOn: PropTypes.string,
124+
validateOn: PropTypes.string,
125+
validators: PropTypes.oneOfType([
126+
PropTypes.func,
127+
PropTypes.object,
128+
]),
129+
errors: PropTypes.oneOfType([
130+
PropTypes.func,
131+
PropTypes.object,
132+
]),
75133
controlProps: PropTypes.object,
76134
component: PropTypes.any,
135+
dispatch: PropTypes.func,
77136
};
78137

79138
Control.defaultProps = {

src/reducers/form-reducer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const initialFieldState = {
2727
untouched: true, // will be deprecated
2828
valid: true,
2929
validating: false,
30+
validated: false,
3031
viewValue: null,
3132
array: false,
3233
validity: {},
@@ -152,6 +153,7 @@ function _createFormReducer(model, initialState) {
152153
dirty: true, // will be deprecated
153154
pristine: false,
154155
value: action.value,
156+
validated: false,
155157
});
156158

157159
if (action.removeKeys) {
@@ -268,6 +270,7 @@ function _createFormReducer(model, initialState) {
268270
errors,
269271
validity: action.validity,
270272
valid: isBoolean(errors) ? !errors : every(errors, error => !error),
273+
validated: true,
271274
});
272275

273276
return icepick.merge(formIsValidState, {
@@ -291,6 +294,7 @@ function _createFormReducer(model, initialState) {
291294
errors: action.errors,
292295
validity,
293296
valid: isValid(validity),
297+
validated: true,
294298
});
295299

296300
return icepick.merge(setErrorsState, {

test/field-component-spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ describe('<Field /> component', () => {
519519
test: modelReducer('test', {
520520
foo: '',
521521
blur: '',
522+
external: '',
522523
}),
523524
}));
524525

@@ -613,6 +614,38 @@ describe('<Field /> component', () => {
613614
}, 'should only validate upon blur');
614615
});
615616

617+
it('should validate on external change', () => {
618+
let timesValidationCalled = 0;
619+
620+
TestUtils.renderIntoDocument(
621+
<Provider store={store}>
622+
<Field
623+
model="test.external"
624+
validators={{
625+
required: (val) => {
626+
timesValidationCalled += 1;
627+
return val && val.length;
628+
},
629+
}}
630+
>
631+
<input type="text" />
632+
</Field>
633+
</Provider>
634+
);
635+
636+
assert.equal(timesValidationCalled, 1,
637+
'validation called on load');
638+
639+
assert.isFalse(store.getState().testForm.fields.external.valid);
640+
641+
store.dispatch(actions.change('test.external', 'valid'));
642+
643+
assert.isTrue(store.getState().testForm.fields.external.valid);
644+
645+
assert.equal(timesValidationCalled, 2,
646+
'validation called because of external change');
647+
});
648+
616649
it('should send the proper model value to the validators', () => {
617650
const field = TestUtils.renderIntoDocument(
618651
<Provider store={store}>

0 commit comments

Comments
 (0)