Formik: return value for validateField

Created on 12 Nov 2019  路  5Comments  路  Source: formium/formik

馃殌 Feature request

Current Behavior

Currently, calling validateField('firstName') returns void (or Promise<void>. Calling validateForm(), on the other hand, returns the object with errors for the entire form.

Desired Behavior

validateField Returning a single key-value pair, i.e. { firstName: 'First name is required' }, if invalid, or undefined if the field is valid.

Suggested Solution

Adding a return value to the function call. Since it is returning Promise<void> right now, I imagine this would not be a breaking change.

Is there any reason in particular that it is set up this way?

Who does this impact? Who is this for?

Users triggering manual validation of fields. My use case is a custom "Wizard", and I want to validate a limited number of fields for each step, without validating fields on further steps. Something like this:

export const ContactInfo = () => {
  const formik = useFormikContext()
  const { validateField } = formik
  const { goToStep } = useSequence()
  const next = async () => {
    const validated = await Promise.all([
      validateField('firstName'),
      validateField('lastName'),
      validateField('emailAddress'),
      validateField('phone'),
      validateField('agreeToEmail'),
    ])
    const invalidFields = validated.filter(v => v !== undefined)
    if (invalidFields.length === 0) goToStep('nextStep')
  }

  return (
    <div>
      <Heading level={3}>Register for your visit</Heading>
      <Field name="firstName" label="First Name" />
      <Field name="lastName" label="Last Name" />
      <Field name="emailAddress" label="Email" type="email" required />
      <Field name="phone" label="Mobile Phone Number" type="tel" required />
      <Field
        name="agreeToEmail"
        type="checkbox"
        label="I agree to receive email of photos/video taken during the Event"
      />
      <button onClick={next}>Continue</button>
    </div>
  )
}

Describe alternatives you've considered

I can use some state to work around this for now, but it's a little hacky. Here's the workaround:

import * as React from 'react'
import { useFormikContext } from 'formik'
import { Field } from '../../components/Forms'
import { useSequence } from '../../components/Sequence'
import { Heading } from '../../components/Text'

const { useState, useEffect } = React

interface ContactValues {
  firstName: string
  lastName: string
  emailAddress: string
  phone: string
  agreeToEmail: boolean
}

export const ContactInfo = () => {
  const { errors, setTouched, validateForm } = useFormikContext<ContactValues>()

  const [shouldProceed, setShouldProceed] = useState(false)
  const { goToStep } = useSequence()
  const next = async () => {
    setTouched({
      firstName: true,
      lastName: true,
      emailAddress: true,
      phone: true,
      agreeToEmail: true,
    })
    await validateForm()
    setShouldProceed(true)
  }

  useEffect(() => {
    if (shouldProceed === false) return
    if (
      errors.firstName ||
      errors.lastName ||
      errors.emailAddress ||
      errors.phone ||
      errors.agreeToEmail
    ) {
      setShouldProceed(false)
      return
    }
    goToStep('nextStep')
  }, [shouldProceed, errors])

  return (
    <div>
      <Heading level={3}>Register for your visit</Heading>
      <Field name="firstName" label="First Name" />
      <Field name="lastName" label="Last Name" />
      <Field name="emailAddress" label="Email" type="email" />
      <Field name="phone" label="Mobile Phone Number" type="tel" />
      <Field
        name="agreeToEmail"
        type="checkbox"
        label="I agree to receive email of photos/video taken during the Event"
      />
      <button type="button" onClick={next}>
        Continue
      </button>
    </div>
  )
}
stale

Most helpful comment

This feature would be immensely useful.

validateField Returning a single key-value pair, i.e. { firstName: 'First name is required' }, if invalid, or undefined if the field is valid.

I'm curious why you think this should return an object rather than just a bare string of the error. It seems like that would be more intuitive, where validateField("some.field[5].value") returns the next value of errors.some.field[5].value

All 5 comments

@good-idea just wanted to thank you for a really well written bug report with an alternative I used to work around an almost identical issue!

This feature would be immensely useful.

validateField Returning a single key-value pair, i.e. { firstName: 'First name is required' }, if invalid, or undefined if the field is valid.

I'm curious why you think this should return an object rather than just a bare string of the error. It seems like that would be more intuitive, where validateField("some.field[5].value") returns the next value of errors.some.field[5].value

I was a bit surprised that this wasn't the behavior of the function already

This is something awesome to have actually. For the given scenario, any other solution would be counter-intuitive. Came across a similar situation where I badly need this feature.

+1 for the feature. This would be extremely helpful when creating custom onBlur functions where I submit or change global state onBlur but need to validate the input first. As it currently stands I have to validate all fields and only then I can change global state or submit that value. Again, this is helpful when I would like to submit data on a field input basis and not have a button click that submits all fields.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ancashoria picture ancashoria  路  3Comments

outaTiME picture outaTiME  路  3Comments

pmonty picture pmonty  路  3Comments

emartini picture emartini  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments