gene b.
gene b.

Reputation: 11994

Show errors both (1) on Submit click and (2) individually on blur/change with 'touched'

In Formik, I need validation errors to be displayed in both cases:

  1. On Change/Blur, individually as fields get touched on the form -- not all at once, during the filling-out process
  2. On clicking Submit at any time: here, all errors should be shown at once.

(1) is working but (2) is not. When I come to the form and just click the Submit button, nothing happens and the errors aren't displayed. The errors only get displayed when I touch the controls.

I think the problem is that to satisfy (1), my controls have

isInvalid={touched.startDate && errors.startDate}  

But when I click Submit immediately, the control hasn't been touched yet, so this isInvalid condition fails. But I can't remove the touched.startDate part because otherwise, all invalid controls always start showing when filling out the form -- even the ones I haven't been touched. I need to keep my touched requirement when filling out, but also show all errors on Submit. The Submit is the one case where all errors have to be shown at once. Is it possible to pass some submitClicked variable somewhere to achieve this?

<Formik enableReinitialize 
        validationSchema={schema}
        onSubmit={ (values, { validate }) => {
            alert(JSON.stringify(values, null, 2));
        }}
        initialValues={{}}
>
   {({
      handleSubmit,
      handleChange,
      handleBlur,
      values,
      touched,
      isValid,
      errors,
     }) => (
         <Form onSubmit={handleSubmit}> 
             ...
             {/* EXAMPLE */}
             <Form.Control type="date"
                           name="expirationDate"
                           value={values.expirationDate}
                           onChange={handleChange}
                           onBlur={handleBlur}
                           isInvalid={touched.expirationDate && errors.expirationDate}    
             </Form.Control>
         </Form>

...
// Yup Schema used for form validation
const schema = yup.object().shape({
    expirationDate: yup.date().required('Expiration Date is required'),
    frequencyDays: yup.number().required('Frequency is required'),
    interval: yup.string().required('Frequency interval is required'),
    ...           

Upvotes: 2

Views: 4636

Answers (3)

Anish Ali
Anish Ali

Reputation: 39

This problem is happening in my app due to api call and showing inputs

initialValues={{
          ...data?.data,
          primary_android: data?.data
            ? data?.data.primary_android
            : 'Art & Design',
          keyword_android: data?.data?.keyword_android
            ? JSON.parse(data.data.keyword_android)
            : [],
          keyword_ios: data?.data?.keyword_ios
            ? JSON.parse(data.data.keyword_ios)
            : [],
          primary_iso: data?.data ? data?.data.primary_iso : 'Art & Design',
          secoundry_iso: data?.data ? data?.data.secoundry_iso : 'Books',
          liveStatus: 'pending'
        }}

as you can see the fields which is type manually primary_android,keyword_android,keyword_ios etc are valid for errors when I click on submit but other fields ...data?.data which are fetching and showing are not be valid for onBlur error when submit so

So the simple answer is if you are using api call for inputs initial data make sure you add every property statically and the problem will be fixed

Upvotes: 1

gene b.
gene b.

Reputation: 11994

Since a lot of people are viewing this thread, here's what we've ended up with. This is our React Bootstrap control example, in this case a Select called frequencyDays inside a Formik. Note the following:

isInvalid={(submitClicked && errors.frequencyDays) || 
           (!submitClicked && touched.frequencyDays && errors.frequencyDays)}

This means that either (1) Submit was clicked and errors exist, or (2) Submit was NOT clicked, but this control was touched, and errors exist.

<Form.Control as="select"
    id="dropdownFrequencyDays"
    name="frequencyDays"
    value={values.frequencyDays}
    onChange={handleChange}
    onBlur={handleBlur}
    isInvalid={(submitClicked && errors.frequencyDays) || (!submitClicked && touched.frequencyDays && errors.frequencyDays)}

SubmitClicked is a variable, initialized/set as follows:

// Shows whether the Submit button was clicked (used to show all form validation errors at once)
// initially FALSE
const [submitClicked, setSubmitClicked] = useState(false);

set in Submit Button's onClick:

<Button type="submit" disabled={isSubmitting} 
   onClick={() => { 
       setSubmitClicked(true); // We set it to TRUE in Submit's onClick
            }} 
   variant="primary">Submit</Button>

If you're using Material UI, it's similar to React-Bootstrap and it has some error property on its controls which is analogous to isInvalid.

Upvotes: 1

Emma
Emma

Reputation: 834

I think you're right that because you have the check to see if a field is touched, the errors aren't rendering after validation. I think a workaround to this could be to programmatically set the state for all fields to touched=true once the onSubmit handler is called.

form.setTouched({...form.touched,[field.name]: true });

There isn't a codesandbox to go off of but something like this

<Formik enableReinitialize 
        validationSchema={schema}
        onSubmit={ (values, { validate }) => {
            // Set the form fields to touched programmatically
            form.setTouched({...form.touched,[field.name]: true });

            alert(JSON.stringify(values, null, 2));
        }}
        initialValues={{}}
>
   {({
      handleSubmit,
      handleChange,
      handleBlur,
      values,
      touched,
      isValid,
      errors,
     }) => (
         <Form onSubmit={handleSubmit}> 
             ...
             {/* EXAMPLE */}
             <Form.Control type="date"
                           name="expirationDate"
                           value={values.expirationDate}
                           onChange={handleChange}
                           onBlur={handleBlur}
                           isInvalid={touched.expirationDate && errors.expirationDate}    
             </Form.Control>
         </Form>

Upvotes: 2

Related Questions