2
votes

I am creating wrapped versions of the React-Final-Form field component, essentially creating shareable types of fields to speed development at my company. As a part of that, I wanted to create a group of checkboxes, requiring at least one to be checked - but when I attempt to write the validate function, I only seem to have access to the current field.

  1. Based on some other answers and the API documentation, I thought that the validate function received three parameters - validateMyField(value, allValues, fieldState).

When I passed my function to a given field, I get the following:

const mustPickAtLeastOne = (value, allValues, fieldState) => {
  console.log(value) //output: false
  console.log(allValues) //output: undefined
  console.log(fieldState) //output: undefined
}
  1. I was able to get the validation functional by simply adding a marker class to my checkbox fields, and then using a validation function like this:
const mustPickAtLeastOne = () => {
  const numberChecked = document.querySelectorAll("." + markerclass + " input[type=\"checkbox\"]:checked").length;
  return (numberChecked === 0 ? atLeastOneRequiredMsg : undefined);
}

This option is functional from a browser perspective, but smells pretty bad (both because I am attempting to interact with the underlying DOM nodes directly, and because I then can't test this via Jest/Enzyme - so I am missing unit test coverage to know if I break this in the future).

I am using React-Final-Form 6.3.0/Final-Form 4.16.1 - is there something wrong elsewhere, or why can't I get allValues in my validate function? Or is there an entirely better way to implement my "require at least one of these" validation?

3

3 Answers

3
votes

Unfortunately, I had been starting off with the example at https://codesandbox.io/s/k10xq99zmr (making use of the composeValidators function) - as I wanted to allow consumers of my component to provide custom validation functions, as well as have the component itself have one or more built-in validations (and then take the combination of all of the above to pass to the underling field validate property)...which meant that the answer was staring me right in the face:

//...
const composeValidators = (...validators) => value =>
  validators.reduce((error, validator) => error || validator(value), undefined);
///...

I corrected it to use the following:

//...
const composeValidators = (...validators) => (value, allValues, fieldState) =>
  validators.reduce((error, validator) => error || validator(value, allValues, fieldState), undefined);
///...

Meaning that despite the underlying Final-Form implementation providing all three arguments to each assigned validator function, my use of the composeValidators sample was passing the current field value along...while throwing out/ignoring the other two.

1
votes

Yes, using classes on the DOM nodes is gross. How about something like this?

const mustCheckAtLeastOne = (...fields) => (_, allValues, state) =>
  fields.some(field => allValues[field]) ? undefined : "Must check one";

Is this not similar to what you want?

Edit 🏁 React Final Form - Field-Level Validation Example

0
votes

Alternatively, I just tried with this example with the form validate prop. https://codesandbox.io/s/react-final-form-simple-example-zu66s

It depends what kind of UI you are trying to do.

const validateForm = values => {
  if (typeof values.sauces === "undefined" || values.sauces.length === 0)
    return { sauces: "must choose one" };
};
// <Form validate={validateForm} ...