Formik Yup validation running before field is touched

Viewed 5989

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 Answers

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

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))
          ),
Related