kentcdodds / ama

Ask me anything!
https://github.com/kentcdodds/ama/issues?q=is%3Aissue+is%3Aclosed
685 stars 75 forks source link

Form Validation Util or Component #558

Closed theskillwithin closed 5 years ago

theskillwithin commented 5 years ago

Hi Kent!!!

Would love it if you did a youtube video on creating a form validation util/helper or component.

I find it's something that is needed on many projects, and want to investigate the best ways to approach this.

Thanks!

kentcdodds commented 5 years ago

Hi @theskillwithin,

Could you be more specific about what you're looking for? If I was doing forms heavily I'd probably just reach for Formik.

theskillwithin commented 5 years ago

yes so in the past usually start making a validation util and it sort of grows over time. and I was hoping to find the best way of accomplishing this. Ill take a look into Formik this might be a solution to this.

/*
  (add this method to your component)
  handleValidate(name, type) {
    const newState = validate(name, type, this.state.form)
    if (newState) this.setState(newState)
  }

  this.handleValidate('email', 'email')
*/

export const regularExpressions = {
  // TODO should change to is{Object} as we are asserting is this value an email, or phone number.
  email: (/@/),
  phone: (/^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$/),
  IBANinternational: (/\w{2}\d{2} ?\d{4} ?\d{4} ?\d{4} ?\d{4} ?\d{2}/),
  UTF8Words: (/([^\u0000-\u0040\u005B-\u0060\u007B-\u00BF\u02B0-\u036F\u00D7\u00F7\u2000-\u2BFF])+/),
  numbers: (/^\d{1,45}$/),
  USDollarWithoutSymbol: (/^\d+(,\d{3})*\.?[0-9]?[0-9]?$/gm),
  notEmpty: (/^(?!\s*$).+/),
  username: (/^[a-zA-Z0-9][a-zA-Z0-9-_+@.]{3,40}$/),
  EIN: (/([0-9]{9})|([0-9]{3}-[0-9]{2}-[0-9]{4})|([0-9]{2}-[0-9]{7})/g),
  // Checks for 2 digit month only
  isMonth: (/^(0[1-9]{1}|1[0-2]{1})$/g),
  // Checks for 4 digit year, or 2 digit year
  isYear: (/^(2\d{3})$/g),
  // Checks for 3 or 4 digit CSC code
  isCsc: (/^\d{3,4}$/g),
  // Checks for Visa, MasterCard, Discover, AMEX, Diners Club, & JCB
  isCCNumber: (/^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/),

  // tax form
  SS: (/\b\d{3}[-]\d{2}[-]\d{4}\b/g),
  USZIP: (/^\d{5}(-?\d{4})?$/gm),
}

const isCurrentYearOrGreater = (year) => {
  const currentTime = new Date()
  const currentYear = currentTime.getFullYear()
  const currentYear2 = currentYear
  const currentYear4 = currentYear
  const validYear = year.length <= 2
    ? !!(year >= currentYear2)
    : !!(year >= currentYear4)

  return validYear
}

const validate = (name, type = 'nonEmpty', form = {}, msg = '', canBeEmpty = false) => {
  const selectRegExp = new RegExp(regularExpressions[type])
  const value = form[name]
  const errorName = `${name}Error`
  const atLeastRegex = /^atLeast\[(\d*?)\]$/

  const error = (err) => {
    return err
      ? { [errorName]: `${msg}` || `Not a valid ${name}. ${msg}` }
      : { [errorName]: '' }
  }
  if (canBeEmpty && (form[name] === '' || form[name] === undefined)) return error(false)

  if (form[name] === '' || form[name] === undefined) {
    return { [errorName]: 'Cannot be empty.' }
  } else if (name === 'year') {
    if (!isCurrentYearOrGreater(value)) {
      return { [errorName]: `Not a valid ${name}. ${msg} & at least the current year.` }
    }
  } else if (atLeastRegex.test(type)) {
    const min = type.match(atLeastRegex)[1]
    return error(Number(value) < min)
  }
  return error(!selectRegExp.test(value))
}

export const passwordMatch = (pass, verify, name = 'password') => {
  const errorName = `${name}Error`
  if (verify !== pass) {
    return { [errorName]: 'Passwords do not match.' }
  }
  return null
}

export const match = (field, verify, name, msg) => {
  const errorName = `${name}Error`
  if (verify !== field) {
    return { [errorName]: `${msg} do not match.` }
  }
  return { [errorName]: false }
}

export default validate