0
votes

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
Is the value in the test function defined? can you log it? My thinking would be that it is not defined when you click on different field, which causing the test function to return false and fail validation when the field is emptyszczocik
Yes, the value is by default undefined but the validation for the image field should not run if I have not touched that field.Nelson King

2 Answers

1
votes

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))
          ),
0
votes

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