If your app already uses redux-form and redux-saga, you can pass validation from the form to a saga through a Promise. Let’s build that bridge, starting with a synchronous check.
For a rule that only checks a field value, redux-form’s synchronous validate function is enough:
reduxForm({
form: 'example',
validate: (values) => {
const errors = {};
if (values.test > 10) {
errors.test = 'Oh no! Test is too big';
}
return errors;
},
});If validation needs external data already available through props, validate(values, props) can still handle it synchronously. To hand work to a saga, we can use asyncValidate, which returns a Promise: resolve it on success or reject it with field errors. See the redux-form v6.2 API reference.
Here, asyncBlurFields selects test as a blur trigger. shouldAsyncValidate allows checks on that blur and on submit; returning true does not trigger validation on every value change or external state update. The rule stays simple so we can focus on the Promise bridge:
const isEmptyObject = (obj) => {
for (let key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
};
reduxForm({
form: 'example',
asyncBlurFields: ['test'],
shouldAsyncValidate: () => true, // Allow checks on configured blur events and submit.
asyncValidate: (values) =>
new Promise((resolve, reject) => {
const errors = {};
if (values.test > 10) {
errors.test = 'Oh no! Test is too big';
}
if (isEmptyObject(errors)) {
resolve();
} else {
reject(errors);
}
}),
});Now move the same check into a saga. The action carries the Promise’s resolve and reject callbacks, so the saga can return the result to redux-form. Reuse isEmptyObject from above:
reduxForm({
form: 'example',
asyncBlurFields: ['test'],
shouldAsyncValidate: () => true, // Allow checks on configured blur events and submit.
asyncValidate: (values, dispatch) =>
new Promise((resolve, reject) =>
dispatch({ type: 'validateExample', values, resolve, reject }),
),
});
function* watchExampleValidation() {
yield takeEvery('validateExample', function* ({ values, resolve, reject }) {
const errors = {};
if (values.test > 10) {
errors.test = 'Oh no! Test is too big';
}
if (isEmptyObject(errors)) {
resolve();
} else {
reject(errors);
}
});
}Import takeEvery from redux-saga/effects and start watchExampleValidation through your existing saga middleware before the form can validate. takeEvery passes each matching action to the worker. Without a running watcher, nothing settles the Promise.






