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

Commit 6acf956

Browse files
committed
Adding meta asyncKeys prop to field to keep track of async validators and allow form to submit when only sync validity is valid. Fixes #567
1 parent 9d354f5 commit 6acf956

File tree

5 files changed

+77
-7
lines changed

5 files changed

+77
-7
lines changed

src/actions/field-actions.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,16 @@ function createFieldActions(s = defaultStrategies) {
123123
model,
124124
});
125125

126-
const asyncSetValidity = (model, validator) => (dispatch, getState) => {
126+
const asyncSetValidity = (model, validator, options = {}) => (dispatch, getState) => {
127127
const value = s.get(getState(), model);
128128

129129
dispatch(setValidating(model, true));
130130

131131
const done = (validity) => {
132-
dispatch(setValidity(model, validity));
132+
dispatch(setValidity(model, validity, {
133+
async: true,
134+
...options,
135+
}));
133136
};
134137

135138
const immediateResult = validator(value, done);
@@ -139,6 +142,12 @@ function createFieldActions(s = defaultStrategies) {
139142
}
140143
};
141144

145+
const asyncSetErrors = (model, validator, options = {}) =>
146+
asyncSetValidity(model, validator, {
147+
errors: true,
148+
...options,
149+
});
150+
142151
const setSubmitted = (model, submitted = true) => ({
143152
type: actionTypes.SET_SUBMITTED,
144153
model,
@@ -297,6 +306,7 @@ function createFieldActions(s = defaultStrategies) {
297306
validateFields,
298307
validateFieldsErrors,
299308
asyncSetValidity,
309+
asyncSetErrors,
300310
addIntent,
301311
clearIntents,
302312
}, trackable);

src/components/form-component.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import actions from '../actions';
1010
import getValidity from '../utils/get-validity';
1111
import invertValidators from '../utils/invert-validators';
1212
import isValidityInvalid from '../utils/is-validity-invalid';
13+
import isValid from '../form/is-valid';
1314
import getForm from '../utils/get-form';
1415
import getModel from '../utils/get-model';
1516
import getField from '../utils/get-field';
16-
import { fieldsValid } from '../form/is-valid';
1717
import deepCompareChildren from '../utils/deep-compare-children';
1818
import containsEvent from '../utils/contains-event';
1919
import invariant from 'invariant';
@@ -150,7 +150,7 @@ function createFormClass(s = defaultStrategy) {
150150
// If the form is invalid (due to async validity)
151151
// but its fields are valid and the value has changed,
152152
// the form should be "valid" again.
153-
if (!formValue.$form.valid && fieldsValid(formValue)) {
153+
if (isValid(formValue, { async: false })) {
154154
dispatch(s.actions.setValidity(model, true));
155155
}
156156

@@ -310,7 +310,7 @@ function createFormClass(s = defaultStrategy) {
310310
case 'submit': {
311311
dispatch(s.actions.clearIntents(model, intent));
312312

313-
if (formValue.$form.valid) {
313+
if (isValid(formValue, { async: false })) {
314314
this.handleValidSubmit();
315315
} else {
316316
this.handleInvalidSubmit();

src/form/is-valid.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import isPlainObject from '../utils/is-plain-object';
22

3-
export default function isValid(formState) {
3+
export default function isValid(formState, options = { async: true }) {
44
if (!formState) return true;
55

66
if (!formState.$form) {
@@ -11,14 +11,23 @@ export default function isValid(formState) {
1111
}
1212

1313
return Object.keys(formState.errors).every((errorKey) => {
14+
// if specified to ignore async validator keys and
15+
// current error key is an async validator key,
16+
// treat key as valid
17+
if (!options.async
18+
&& formState.asyncKeys
19+
&& !!~formState.asyncKeys.indexOf(errorKey)) {
20+
return true;
21+
}
22+
1423
const valid = !formState.errors[errorKey];
1524

1625
return valid;
1726
});
1827
}
1928

2029
return Object.keys(formState)
21-
.every((key) => isValid(formState[key]));
30+
.every((key) => isValid(formState[key], options));
2231
}
2332

2433
export function fieldsValid(formState) {

src/reducers/form-actions-reducer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ export default function formActionsReducer(state, action, localPath) {
189189
: isValidityValid(validity)),
190190
};
191191

192+
if (action.async) {
193+
fieldUpdates.asyncKeys = Object.keys(isErrors ? action.errors : action.validity);
194+
}
195+
192196
parentFormUpdates = (form) => ({ valid: isValid(form) });
193197

194198
break;

test/form-component-spec.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,5 +2059,52 @@ Object.keys(testContexts).forEach((testKey) => {
20592059
);
20602060
});
20612061
});
2062+
2063+
describe('deep async validity', () => {
2064+
const initialState = getInitialState({ foo: '' });
2065+
2066+
const store = testCreateStore({
2067+
test: modelReducer('test', initialState),
2068+
testForm: formReducer('test', initialState),
2069+
});
2070+
2071+
const handleSubmit = sinon.spy((val) => val);
2072+
2073+
TestUtils.renderIntoDocument(
2074+
<Provider store={store}>
2075+
<Form
2076+
model="test"
2077+
onSubmit={handleSubmit}
2078+
>
2079+
<Control model=".foo" />
2080+
</Form>
2081+
</Provider>
2082+
);
2083+
2084+
beforeEach(() => {
2085+
store.dispatch(actions.reset('test'));
2086+
});
2087+
2088+
it('should allow submit if non-async validity is valid', () => {
2089+
store.dispatch(actions.setValidity('test.foo', { asyncValid: false }, { async: true }));
2090+
store.dispatch(actions.setValidity('test.foo', { syncValid: false }, { merge: true }));
2091+
2092+
assert.isFalse(store.getState().testForm.foo.valid);
2093+
2094+
store.dispatch(actions.submit('test'));
2095+
2096+
assert.isFalse(handleSubmit.calledOnce,
2097+
'not called because sync validity is invalid');
2098+
2099+
store.dispatch(actions.setValidity('test.foo', { syncValid: true }, { merge: true }));
2100+
2101+
assert.isFalse(store.getState().testForm.foo.valid);
2102+
2103+
store.dispatch(actions.submit('test'));
2104+
2105+
assert.isTrue(handleSubmit.calledOnce,
2106+
'called because sync validity is valid');
2107+
});
2108+
});
20622109
});
20632110
});

0 commit comments

Comments
 (0)