Formik Yup validation running before field is touched

9.8k views Asked by At

I have a form that has a field items, which stores an array of objects with the properties image and name. The image property accepts a file but is optional, the name field is required.

For some reason, when I touch the name field, the image field validation kicks in and it throws the error "Image is too large", which can be seen from formik's errors object. I don't understand why this is happening. Below is the code for the component and a codesandbox which you can view here https://codesandbox.io/s/new-fog-xs2wb?file=/src/components/form.js

import React from 'react';
import { Formik, Field, FieldArray } from 'formik';
import * as Yup from 'yup';

function MyForm(props) {
  const FILE_SIZE = 5 * 1024 * 1024;
  const SUPPORTED_FORMATS = ["image/jpg", "image/jpeg", "image/gif", "image/png"];

  const validationSchema = Yup.object().shape({
    items: Yup.array().of(Yup.object().shape({
      image: Yup.mixed()
          .test("fileSize", "Image is too large", (value) => value && value.size <= FILE_SIZE)
          .test(
            "fileFormat",
            "Unsupported Format - We only allow images.",
            (value) => value && SUPPORTED_FORMATS.includes(value.type)
          ),
       name: Yup.string().required('Required') 
    }))
  });

  const initialValues = { items: [{
    image: undefined,
    name: ''
  }]}

  return (
    <div className="container">
      <Formik enableReinitialize={true}
              initialValues={initialValues}
              validationSchema={validationSchema}
              onSubmit={async (values, { setSubmitting, resetForm }) => {
                setSubmitting(true);
                console.log(values);
                setSubmitting(false);
              }}>
              {({
                values,
                errors,
                touched,
                handleChange,
                handleBlur,
                handleSubmit,
                isSubmitting,
                setFieldValue,
              }) => (
                <form className="mt-3" onSubmit={handleSubmit}>
                    <FieldArray name="items">
                    {({ push, remove }) => (
                      <React.Fragment>
                      {values.items && values.items.length && values.items.map((item, index) => (
                        <div key={index}>
                          {/* <Field name={`items[${index}].image`} /> */}
                          <div className="form-group">
                              <label htmlFor="name">Name</label>
                              <input name={`items[${index}].name`} className="form-control" id="name" aria-describedby="nameHelp" onChange={handleChange} onBlur={handleBlur} />
                              <small id="nameHelp" className="form-text text-muted">Enter a descriptive name for the item.</small>
                          </div>
                          <button type="button" className="btn btn-danger mr-2 mb-2" onClick={() => remove(index)}>Remove</button>
                        </div>
                      ))}
                      <button type="button" className="btn btn-secondary mr-2 mt-4" onClick={() => push({image: undefined, name: ''})}>Add</button>
                      <button type="submit" disabled={isSubmitting} className="btn btn-primary mt-4">Submit</button>
                      <pre className="mt-2">{JSON.stringify(errors, null, 2)}</pre>
                      </React.Fragment>
                    )}
                    </FieldArray>
                </form>
              )}
      </Formik>
    </div>)
}

export default MyForm;

How do I get this to work as desired? Your help is greatly appreciated.

2

There are 2 answers

3
Aniruddha Shevle On BEST ANSWER

The function that you used inside the test method should return false to show the resp error msg. Ref: https://github.com/jquense/yup#mixedtestoptions-object-schema

So you should do !(value?.size > FILE_SIZE) and
!(value && !SUPPORTED_FORMATS.includes(value.type)) to make it work!

Code updates:

image: Yup.mixed()
          .test(
             "fileSize", 
             "Image is too large",
             (value) => !(value?.size > FILE_SIZE)
          )
          .test(
            "fileFormat",
            "Unsupported Format - We only allow images.",
            (value) => !(value && !SUPPORTED_FORMATS.includes(value.type))
          ),
1
Piyush Rana On

Formik provides many utilities for validation, you can use below combo for your purpose:

validateOnChange={false}
validateOnBlur={true}

This will fix your problem, if any case it will not work then remove validateOnBlur