Reactjs - Material UI- reduc form framework - grouped checkbox required validation error fixing to have at least one checkbox required

Viewed 1852

enter image description here

I am working on a react project and have built a form framework which wraps material ui, around the redux form.

** sandbox https://codesandbox.io/s/condescending-banzai-re6l3

I am unsure how fix a bug - where if the grouped checkbox field is set as validate - to only have a required message appear under the group of checkboxes - then demand all checkboxes to be required.

Here is the current code base

//renderGroupedCheckboxes

import React from "react";
import { Field } from "redux-form";

import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import FormGroup from "@material-ui/core/FormGroup";
//import FormHelperText from '@material-ui/core/FormHelperText';

import renderCheckboxField from "./renderCheckboxField";

const renderGroupedCheckboxes = (fields) => (
  <FormControl component="fieldset" fullWidth={true}>
    <FormLabel component="legend">{fields.label}</FormLabel>
    <FormGroup row>
      {fields.names.map((item, j) => {
        return (
          <Field
            key={j}
            name={item}
            component={renderCheckboxField}
            label={fields.options[j].label}
            onChange={function (e, newVal) {
              fields.onHandle(e.target.name, newVal);
            }}
          />
        );
      })}
    </FormGroup>
  </FormControl>
);

export default renderGroupedCheckboxes;

the individual checkbox

    //renderCheckboxField
    
    import React from "react";
    import Checkbox from "@material-ui/core/Checkbox";
    import FormControl from "@material-ui/core/FormControl";
    import FormControlLabel from "@material-ui/core/FormControlLabel";
    import FormHelperText from "@material-ui/core/FormHelperText";
    
    const renderCheckboxField = ({
      input,
      rows,
      multiline,
      label,
      meta: { touched, error, warning }
    }) => (
      <FormControl component="fieldset">
        <FormControlLabel
          label={label}
          control={<Checkbox />}
          checked={input.value ? true : false}
          {...input}
        />
        <FormHelperText error={error && error.length > 0 ? true : false}>
          {(error && error.length > 0 ? error : null) ||
            (warning && warning.length > 0 ? warning : null)}
        </FormHelperText>
      </FormControl>
    );

export default renderCheckboxField;
3 Answers

Modify renderGroupedCheckboxes.js. We are checking if any of the checkbox is touched and all the checkboxes are having error, then render the error text

import React from "react";
import { Field } from "redux-form";

import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
import FormGroup from "@material-ui/core/FormGroup";
import FormHelperText from "@material-ui/core/FormHelperText";

import renderGroupedCheckboxField from "./renderGroupedCheckboxField";

const renderGroupedCheckboxes = (fields) => {
  let error = null;
  let isAnyFieldTouched = false;
  let isAllFieldsError = true;
  
  for (let name of fields.names) {
    //if touched is true for any one field, isAnyFieldTouched will
    //be true 
    isAnyFieldTouched = isAnyFieldTouched || fields[name].meta.touched;

    if (
      error === null &&
      fields[name].meta.error &&
      fields[name].meta.error.length > 0
    ) {
      error = fields[name].meta.error;
    }

    //if invalid is false for any one field, isAllFieldsError will
    //be false 
    isAllFieldsError = isAllFieldsError && fields[name].meta.invalid;
  }

  return (
    <FormControl component="fieldset" fullWidth={true}>
      <FormLabel component="legend">{fields.label}</FormLabel>
      <FormGroup row>
        {fields.names.map((item, j) => {
          return (
            <Field
              key={j}
              name={item}
              component={renderGroupedCheckboxField}
              label={fields.options[j].label}
              onChange={function (e, newVal) {
                fields.onHandle(e.target.name, newVal);
              }}
            />
          );
        })}
      </FormGroup>
        <FormHelperText error={error && error.length > 0 ? true : false}>
          {isAnyFieldTouched && isAllFieldsError && error && error.length > 0
            ? error
            : null}
        </FormHelperText>
    </FormControl>
  );
};

export default renderGroupedCheckboxes;

Create renderGroupedCheckboxField.js as below. The reason we are creating new file it to not impact current behavior of checkbox field.

import React from "react";
import Checkbox from "@material-ui/core/Checkbox";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";

const renderGroupedCheckboxField = ({
  input,
  rows,
  multiline,
  label,
  meta: { touched, error, warning }
}) => (
  <FormControl component="fieldset">
    <FormControlLabel
      label={label}
      control={<Checkbox />}
      checked={input.value ? true : false}
      {...input}
    />
  </FormControl>
);

export default renderGroupedCheckboxField;

sandbox - https://codesandbox.io/s/funny-fast-f3i12?file=/src/_SharedFormComponents/renderGroupedCheckboxes.js

I hope I understand the question correctly, however you could use something like this and invalidate the general label instead of putting "required" under each checkbox.

I have found a solution here.

Let me first explained.

When you have names as an Array that mean its a group of inputs.

So after I have checked that, next I will have to see if at least one of the item in the group passed the validation.

After that I will have to make sure that the group is of type CheckBox or RadioButton because when they are then it make sense that at least only one of the item must be selected.

So I made a simple change to validateHandler and made that work.

look below and here is the edited version on SandBox

validateHandler(values) {
    //console.log("values",values);
    //console.log("this.props.fields", this.props.fields);

    const errors = {};

    for (let i = 0; i < this.props.fields.length; ++i) {
      let field = this.props.fields[i];

      var passedValues = false;
      for (let j = 0; j < field.name.length; ++j) {
        let fieldName = field.name[j];
        if (field.validate !== undefined) {
          if (field.validate.includes("email")) {
            //email
            if (
              !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(
                values[fieldName]
              )
            ) {
              errors[fieldName] = "Invalid email address";
            }
          }

          if (field.validate.includes("minLength")) {
            //minLength
            if (
              values[fieldName] !== undefined &&
              values[fieldName].length < 3
            ) {
              errors[fieldName] = "Must be 3 characters or more";
            }
          }

          if (field.validate.includes("required")) {
            //required
            if (!values[fieldName] && typeof values[fieldName] !== "number") {
              errors[fieldName] = "Required";
            } else {
              passedValues = true;
            }
          }
        }
      }

      // when at least one value passed of the group
      if (passedValues && (field.type === "checkbox" || field.type === "radiobutton")
      ) {
        for (let j = 0; j < field.name.length; ++j) {
          var fieldName = field.name[j];
          if (errors[fieldName] !== null) delete errors[fieldName];
        }
      }
    }

Related