Formik: [Next] What's the best way to handle FieldArray validation?

Created on 30 Jan 2018  ·  9Comments  ·  Source: formium/formik

Bug, Feature, or Question?

Question

Info

Since you need to have no errors on the array does this make the most sense for validating Arrays or am I making this too difficult? If you don't return undefined for the array, the submit will not work.

products is an array with each object having a name, cases, and bottles.

export const validate = values => {
  const errors = {};

  if (!values.customer) errors.customer = 'Required';
  if (!values.date) errors.date = 'Required';

  const productErrors = [];
  values.products.forEach(product => {
    let errors = {};

    if (!product.name) errors.name = 'Required';
    if (!product.cases && !product.bottles) {
      errors.cases = 'Required';
      errors.bottles = 'Required';
    }

    productErrors.push(errors);
  });

  if (productErrors.some(errorObject => !isEmpty(errorObject))) {
    errors.product = productErrors;
  } else {
    errors.product = undefined;
  }

  return errors;
};
  • Formik Version: 0.11.0-rc2

Most helpful comment

Check out the latest version.

All 9 comments

You could maybs make it less imperative with like map and reduce but this seems fine to me. Tbh, I’m still exlloring “best practices” with arrays too

Check out the latest version.

I noticed that remove(i) touches the array, while push({}) doesn't. Are you getting this as well? Is this intended behavior?

FieldArray name="names"...
() => push({ name: 'David' }) // touched = {};
() => remove(index) // touched = { names: [] }

So this is what I found for showing errors with FieldArrays based on touched and errors! I don't know the perf impact on this function, but I see a lot of people having this issue in the future.

function getError(touched, errors, name) {
  return (touched[name] && errors[name]) || (getNested(touched, name) && getNested(errors, name));
}

function getNested(theObject, path, separator) {
  try {
    separator = separator || '.';

    return path
      .replace('[', separator)
      .replace(']', '')
      .split(separator)
      .reduce(function(obj, property) {
        return obj[property];
      }, theObject);
  } catch (err) {
    return undefined;
  }
}

const error = getError(touched, errors, name);

<Input
  fieldHasError={!!error}
  errorText={error}
/>

This has finally solved everything for me with Formik!!

Thanks so much for all your hard work @jaredpalmer

Should I just export dlv as get from the internals?

Spoke too soon. This is a new one. With this object, handleSubmit will not work. No errors, no trying to submit. The errors object is empty, so why wouldn't handleSubmit work?

{
  "values": {
    "customer": {
      "id": "1545",
      "description": "FRUGAL MACDOOGAL"
    },
    "date": "2018-02-09",
    "notes": "",
    "followup": "",
    "products": [
      {
        "name": {
          "id": "11810",
          "description": "FIREBALL (100M)"
        }
      }
    ]
  },
  "errors": {},
  "touched": {
    "customer": {
      "id": true,
      "description": true
    },
    "date": true,
    "notes": true,
    "followup": true,
    "products": [
      {
        "name": {
          "id": true,
          "description": true
        }
      }
    ]
  }
}

My validate

export const validate = values => {
  const errors = {};

  if (!values.customer) errors.customer = 'Required';
  if (!values.date) errors.date = 'Required';

  const productErrors = [];
  values.products.forEach(product => {
    let errors = {};

    if (!product.name) errors.name = 'Required';

    productErrors.push(errors);
  });

  if (productErrors.some(obj => !isEmpty(obj))) {
    errors.products = productErrors;
  } else {
    errors.products = undefined;
  }

  return errors;
};

@jaredpalmer Under Formik's _this.submitForm, isValid = Object.keys(maybePromisedErrors).length === 0 is showing isValid as false, and maybePromisedErrors = {products: undefined}

Shouldn't undefined make isValid = true?

EDIT: Fixed by getting rid of the else statement.

@jaredpalmer also wanted to add I just used nested fieldarrays and they work fine.

<Field name={`products[${i}].formats[${j}].qpc`} label="QPC" component={Form.Input} />

I have the same problem, I can not set the fieldarray as touched, setFieldTouched creates as object and not as array. And when I push he also does not arrow as touched.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jungwoo-An picture Jungwoo-An  ·  3Comments

jordantrainor picture jordantrainor  ·  3Comments

jeffbski picture jeffbski  ·  3Comments

jaredpalmer picture jaredpalmer  ·  3Comments

najisawas picture najisawas  ·  3Comments