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