• Using redux-form validation with redux-saga

    5ade20d8413bapexels photo 860379

    If you work with redux apps a lot, you definitely heard about the redux-form package, because working with forms without it is a pain. If not, you should know it simplifies form creation a lot. Even the complex multi-step forms with nested fields could be implemented almost effortlessly using it. The API of the package was recently revamped and it’s super fun to use now, but it’s also pretty big and you definitely can use some hints on it! :) 

    Let’s look at a simple example. It’s not a big deal to make a synchronous validation using the redux-form:

    reduxForm({
      form: 'example',
      validate: values => {
        const errors = {};
        if (values.test > 10) {
          errors.test = 'Oh no! Test is too big';
        }
        return errors;
      },
    }),

    But what can we do if we need to change validation rules based on some state external to the form? There is an “asyncValidate” function for this case. It could be targeted to watch only for some of the fields, but it’s also could completely replace the old good “validate” function:

    const isEmptyObject = obj => {
      for (let key in obj) {
        if (obj.hasOwnProperty(key))
          return false;
      }
      return true;
    };
    
    ...
    
    reduxForm({
      form: 'example',
      shouldAsyncValidate: () => true, // we want to call it every time
      asyncValidate: values => new Promise((resolve, reject) => {
        const errors = {};
        if (values.test > 10) {
          errors.test = 'Oh no! Test is too big';
        }
        if (isEmptyObject(errors)) {
          reject(errors);
        }
        resolve();
      }),
    }),

    If you already use “redux-saga”, you may want to move all this logic into the saga. It’s a step ahead:

    reduxForm({
      form: 'example',
      shouldAsyncValidate: () => true, // we want to call it every time
      asyncValidate: (values, dispatch) => 
        new Promise((resolve, reject) =>
          dispatch({ type: 'validateExample', values, resolve, reject }),
    }),
    
    // and in your saga -->
    
    yield takeEvery('validateExample', function* ({ values, resolve, reject }) {
      const errors = {};
      if (values.test > 10) {
        errors.test = 'Oh no! Test is too big';
      }
      if (isEmptyObject(errors)) {
        reject(errors);
      }
      resolve();
    });