final-form/react-final-form-arrays

Asynchronous `validate` prop of `FieldArray` put unresolved promise as `FINAL_FORM/array-error` instead of resolved value

usavkov-epam opened this issue · 1 comments

Are you submitting a bug report or a feature request?

bug report

What is the current behavior?

If we provide an asynchronous function as validate prop to FieldArray component to perform asynchronous validation of field array itself, then unresolved promise is set as FINAL_FORM/array-error for this field. This causes problems if we want to check the form for errors or display an error message because the promise, even if it resolves to "undefined", is in the form's errors object.

What is the expected behavior?

FieldArray's asynchronous validate function resolved with:

  • undefined (null) - field is valid, no errors set as FINAL_FORM/array-error
  • otherwise - resolved value set as FINAL_FORM/array-error

StackBlitz Link

https://stackblitz.com/edit/react-ts-57t92d?file=App.tsx,components%2FRepeatableField.tsx

What's your environment?

{
    "final-form": "^4.20.7",
    "final-form-arrays": "^3.0.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-final-form": "^6.5.9",
    "react-final-form-arrays": "^3.1.4",
  }

Other information

Synchronous validation work as expected, but not async.

[edit]

Unfortunately the workaround (with useState) is not enough, the form sees the promise stored as an error and never state as valid so.

It seems that making the validator async in useFieldArray works:

cf.

const validate: FieldValidator = useConstant(
() => (value, allValues, meta) => {
if (!validateProp) return undefined
const error = validateProp(value, allValues, meta)
if (!error || Array.isArray(error)) {
return error
} else {
const arrayError = []
// gross, but we have to set a string key on the array
;((arrayError: any): Object)[ARRAY_ERROR] = error
return arrayError
}
}
)

const validate: FieldValidator = useConstant(
    // !!! First change, make validate a Promise
    () => async (value, allValues, meta) => {
      if (!validateProp) return undefined;
      // !!! Second change, resolve eventual Promise before testing if it's a valid error or an array
      const error = await Promise.resolve(validateProp(value, allValues, meta));

      if (!error || Array.isArray(error)) {
        return error;
      }
      const arrayError = [];
      // gross, but we have to set a string key on the array
      ((arrayError: any): Object)[ARRAY_ERROR] = error;

      return arrayError;
    }
  );

[archive]:
Thanks for the workaround (cf. StackBlitz link above).

For the record:

  // `meta` from FieldArray render prop
  const { error } = meta;
  const [resolvedError, setResolvedError] = React.useState();

  React.useEffect(() => {
    Promise.resolve(error).then(setResolvedError);
  }, [error]);