Search code examples
javascriptnode.jsvalidationmomentjsyup

Yup compares min max date with build date instead of current date


So this is fairly strange and I can't understand where it's coming from.

I have a signup WS (in Node.js using Sales.js) where the minimum required age is 18 years old, the validation is done by Yup.

This is my yup schema:

const yup = require('yup');
const moment = require('moment');

const birthdate = yup.date()
  .typeError('INVALID_DATE')
  .min(moment().subtract(115, 'years'), 'MIN_AGE')
  .max(moment().endOf('day').subtract(18, 'years'), 'MAX_AGE');

module.exports = {
  birthdate
};

As you can see from the example underneath, when I put today's date, it is comparing it to 2002-07-31 while it's supposed to be compared with moment().endOf('day').subtract(18, 'years') which would be 2002-08-12

Note that 2002-07-31 is the date in which I built the project. So if I recompile it now, I will no longer have that problem for today. If I retest tomorrow, it will compare it with today’s date! I have no idea how is this possible or how to fix it.

This is the error from yup:

"errors": [{
  "field": "birthdate",
  "code": "E_MAX_AGE_BIRTHDATE",
  "params": {
    "path": "birthdate",
    "value": "2002-08-12T10:00:00.000Z",
    "originalValue": "2002-08-12T10:00:00.000Z",
    "max": "2002-07-31T21:59:59.999Z"
  }
}]

Any ideas? Versions used: moment 2.24.0 ; yup 0.29.2


Solution

  • Okay so I found the solution.

    As a matter of a fact, Yup will store the date you initially gave and compile it. It will no longer execute moment().endOf('day').subtract(18, 'years') each time you validate a date.

    For this, you have to use a test method, not min nor max.

    I have created an extension method to yup.date as following:

    /**
     * This function is used to format the error like date.min does
     */
    function customValidationError(errorMessage, value, path, extraData) {
      const error = new yup.ValidationError(errorMessage,
        value,
        path);
      error.params = {
        path: path,
        value: value,
        originalValue: value,
        ...extraData
      };
      return error;
    }
    
    /**
     * Extension method to yup.date
     * You should pass a function that will be executed each time you want to check again the current date.
     * @name yup.dynamicMaxDate
     * @global yup.dynamicMaxDate
     */
    yup.addMethod(yup.date, 'dynamicMaxDate', function(path, errorMessage = 'MAX_DATE', maxDateFunction) {
      return this.test(path, errorMessage,
        (value) => {
          const maxDate = maxDateFunction();
          return new Promise((resolve, reject) => {
            const error = customValidationError(errorMessage, value, path, {
              max: maxDate
            });
            value && moment(value).isAfter(maxDate) ?
              reject(error) :
              resolve(true);
          });
        });
    });
    
    /**
     * Extension method to yup.date to check if date is before a given date
     * You should pass a function that will be executed each time you want to check again the current date.
     * @name yup.dynamicMinDate
     * @global yup.dynamicMinDate
     */
    yup.addMethod(yup.date, 'dynamicMinDate', function(path, errorMessage = 'MIN_DATE', minDateFunction) {
      return this.test(path, errorMessage,
        (value) => {
          const minDate = minDateFunction();
          return new Promise((resolve, reject) => {
            const error = customValidationError(errorMessage, value, path, {
              min: minDate
            });
            value && moment(value).isBefore(minDate) ?
              reject(error) :
              resolve(true);
          });
        });
    });

    And to use those methods:

    const birthdate = yup.date()
      .typeError('INVALID_DATE')
      // Note tham I'm passing a function to be executed when validating date
      .dynamicMinDate('birthdate', 'MIN_AGE', () => {
        return moment().startOf('day').subtract(115, 'years').toDate();
      })
      .dynamicMaxDate('birthdate', 'MAX_AGE', () => {
        return moment().endOf('day').subtract(18, 'years').toDate();
      });