formik icon indicating copy to clipboard operation
formik copied to clipboard

return value for validateField

Open good-idea opened this issue 5 years ago • 19 comments

🚀 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>
  )
}

good-idea avatar Nov 12 '19 21:11 good-idea

@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!

msukmanowsky avatar Jan 23 '20 18:01 msukmanowsky

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

1000hz avatar Feb 20 '20 19:02 1000hz

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

EricVanDerDijs avatar Dec 08 '20 00:12 EricVanDerDijs

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.

rajeev-k-tomy avatar Dec 27 '20 14:12 rajeev-k-tomy

+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.

laffed avatar Dec 28 '20 21:12 laffed

+1 for this feature. My situation is like validating some fields in one of the sections of the form before going to next section, After running for loop validateField() of those fields, the formik instance is not updated with the latest errors, so no errors caught and thus passed to next section and missed some mandatory fields in the previous section.

jagwingchoy avatar Feb 22 '21 08:02 jagwingchoy

+1 for this feature. Facing the same situation as @jagwingchoy

ls-miles-rostami avatar Mar 01 '21 19:03 ls-miles-rostami

+1 for this feature. My situation is like validating some fields in one of the sections of the form before going to next section, After running for loop validateField() of those fields, the formik instance is not updated with the latest errors, so no errors caught and thus passed to next section and missed some mandatory fields in the previous section.

@johnrom Sorry to quote you here as this discussion is not being aware. I do hope there is some solution or suggestion to handle the situation, thanks.

jagwingchoy avatar Mar 09 '21 08:03 jagwingchoy

This is something I ran into when trying to validate a "resend confirmation email" button. There were two fields, email and code, and I wanted to validate both of them on form submit, but only validate email when clicking the resend button. Since validateField() doesn't have a proper callback and the errors object doesn't update, I ended up using setFieldTouched:

<button type="button" disabled={isSubmitting} onClick={() => {
  setFieldTouched('email', true, true).then(err => {
    if (!err.hasOwnProperty("email")) {
      handleResend(values);
    }
  });
}}>Re-send confirmation code</button>

rawsh avatar Apr 13 '21 19:04 rawsh

+1 for this feature.

joaopmo avatar Nov 30 '21 13:11 joaopmo

+1 for this feature

Svudec avatar Mar 23 '22 14:03 Svudec

+1 for this feature

mindmind avatar May 04 '22 17:05 mindmind

+1 for this feature

MagnusSafty avatar Jun 21 '22 08:06 MagnusSafty

+1 this would be helpful

jaredhanson11 avatar Sep 20 '22 21:09 jaredhanson11

+1 for this feature

YariMVSage avatar Oct 19 '22 11:10 YariMVSage

+1 this would be very helpful

henrynoowah avatar Oct 27 '22 03:10 henrynoowah

This is something I ran into when trying to validate a "resend confirmation email" button. There were two fields, email and code, and I wanted to validate both of them on form submit, but only validate email when clicking the resend button. Since validateField() doesn't have a proper callback and the errors object doesn't update, I ended up using setFieldTouched:

<button type="button" disabled={isSubmitting} onClick={() => {
  setFieldTouched('email', true, true).then(err => {
    if (!err.hasOwnProperty("email")) {
      handleResend(values);
    }
  });
}}>Re-send confirmation code</button>

this is a good workaround for me. another thing is, setFieldTouched('email', true, true) triggered validation for the whole form. so if there is another input field for confirmation code, its error state will become true. I had to do error={formik.touched.code && Boolean(formik.errors.code)} to prevent premature showing the error

sumtsui avatar Jun 24 '23 10:06 sumtsui

+1 for this feature.

webgev avatar Oct 09 '23 12:10 webgev

+1

DerekWang98 avatar Mar 04 '24 23:03 DerekWang98