reactjsreact-final-formfinal-formreact-final-form-arrays

Final form array fields async validation


I am trying to implement async validation react-final-form-array. But its returning promise but not showing error at all.

I took reference of this code for asnyc validate and created my own version with final form array here.

Form:

<Form
  onSubmit={onSubmit}
  mutators={{
    ...arrayMutators
  }}
  initialValues={{ customers: [{ placeholder: null }] }}
  validate={formValidate}
  render={({
    handleSubmit,
    mutators: { push, pop }, // injected from final-form-arrays above
    pristine,
    reset,
    submitting,
    values,
    errors
  }) => {
    return (
      <form onSubmit={handleSubmit}>
        <div>
          <label>Company</label>
          <Field name="company" component="input" />
        </div>
        <div className="buttons">
          <button type="button" onClick={() => push("customers", {})}>
            Add Customer
          </button>
          <button type="button" onClick={() => pop("customers")}>
            Remove Customer
          </button>
        </div>
        <FieldArray name="customers" validate={nameValidate}>
          {({ fields }) =>
            fields.map((name, index) => (
              <div key={name}>
                <label>Cust. #{index + 1}</label>
                <Field
                  name={`${name}.firstName`}
                  component={testInput}
                  placeholder="First Name"
                />
                <Field
                  name={`${name}.lastName`}
                  component={testInput}
                  placeholder="Last Name"
                />
                <span
                  onClick={() => fields.remove(index)}
                  style={{ cursor: "pointer" }}
                >
                  ❌
                </span>
              </div>
            ))
          }
        </FieldArray>

        <div className="buttons">
          <button type="submit" disabled={submitting || pristine}>
            Submit
          </button>
          <button
            type="button"
            onClick={reset}
            disabled={submitting || pristine}
          >
            Reset
          </button>
        </div>
        <pre>
          nameValidate Function:{"\n"}
          {JSON.stringify(nameValidate(values.customers), 0, 2)}
        </pre>
        <pre>
          Form errors:{"\n"}
          {JSON.stringify(errors, 0, 2)}
        </pre>
        <pre>
          Form values:{"\n"}
          {JSON.stringify(values, 0, 2)}
        </pre>
      </form>
    );
  }}
/>

Validate Function:

const duplicateName = async value => {
  await sleep(400);
  if (
    ~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
  ) {
    return { firstName: "name taken!" };
  }
};

const nameValidate = values => {
  if (!values.length) return;

  const errorsArray = [];

  values.forEach(value => {
    if (value) {
      let errors = {};

      if (!value.firstName) errors.firstName = "First Name Required";
      if (Object.keys(errors).length === 0) {
        errors = duplicateName(value.firstName);
        console.log("errors", errors);
      }
      errorsArray.push(errors);
    }
  });

  return errorsArray;
};

Solution

  • Okay, you're mixing the validation in a way that's not allowed. You can't embed a Promise inside an array of errors, the whole validation function must return a promise. Here's the fixed version. The await duplicateName() is the important bit.

    const nameValidate = async values => {
      if (!values.length) return
    
      return await Promise.all(
        values.map(async value => {
          let errors = {}
          if (value) {
            if (!value.firstName) errors.firstName = 'First Name Required'
            if (Object.keys(errors).length === 0) {
              errors = await duplicateName(value.firstName)
            }
          }
          console.error(errors)
          return errors
        })
      )
    }