Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return value for validateField #2021

Open
good-idea opened this issue Nov 12, 2019 · 22 comments · May be fixed by #4013
Open

return value for validateField #2021

good-idea opened this issue Nov 12, 2019 · 22 comments · May be fixed by #4013
Labels

Comments

@good-idea
Copy link

good-idea commented Nov 12, 2019

🚀 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 stale bot added the stale label Jan 11, 2020
@msukmanowsky
Copy link

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

@stale stale bot removed the stale label Jan 23, 2020
@1000hz
Copy link

1000hz commented Feb 20, 2020

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

@stale stale bot added the stale label Apr 23, 2020
@EricVanDerDijs
Copy link

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

@rajeev-k-tomy
Copy link

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.

@laffed
Copy link

laffed commented Dec 28, 2020

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

@jagwingchoy
Copy link

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

@ls-miles-rostami
Copy link

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

@jagwingchoy
Copy link

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

@rawsh
Copy link

rawsh commented Apr 13, 2021

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>

@joaopmo
Copy link

joaopmo commented Nov 30, 2021

+1 for this feature.

@Svudec
Copy link

Svudec commented Mar 23, 2022

+1 for this feature

2 similar comments
@mindmind
Copy link

mindmind commented May 4, 2022

+1 for this feature

@MagnusSafty
Copy link

+1 for this feature

@jaredhanson11
Copy link

+1 this would be helpful

@YariMVSage
Copy link

+1 for this feature

@henrynoowah
Copy link

+1 this would be very helpful

@sumtsui
Copy link

sumtsui commented Jun 24, 2023

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

@webgev
Copy link

webgev commented Oct 9, 2023

+1 for this feature.

@DerekWang98
Copy link

+1

@oncet
Copy link

oncet commented Dec 4, 2024

Facing this same issue, expected this function to return the actual error(s).

@donelv
Copy link

donelv commented Dec 5, 2024

more than 5 years have passed, still waiting...

@sumtsui sumtsui linked a pull request Dec 8, 2024 that will close this issue
@oncet
Copy link

oncet commented Dec 10, 2024

Just found out that validateForm() does return errors, then why validateField() doesn't? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.