1

I'm using Ionic 7, React 18, and REact hook forms v 7.48. I would like to validate my form upon submit, but also display error messages if the user touches the control and doesn't enter the input correctly. I constructed my form like so ...

  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm();
    ...
      <form onSubmit={handleSubmit(handleSaveAndContinue)}>
        ...
        <Controller
          name="firstName"
          control={control}
          rules={{
            required: { value: true, message: "Required" },
          }}
          render={({ field }) => (
            <RequiredTextInput
              label="Last Name"
              placeholder="Last Name"
              initialValue={contactInfo.lastName}
              onChange={(lastName) =>
                setContactInfo({ ...contactInfo, lastName })
              }
            />
          )}
        />
    ...
        <IonButton type="submit">Save and Continue</IonButton>
      </form>

and defined this text input control to take advantage of Ionic's ability to display an error message ...

function RequiredTextInput({
  label,
  placeholder,
  initialValue = "",
  onChange,
}: RequiredTextInputProps) {
  const [isTouched, setIsTouched] = useState(false);
  const [isValid, setIsValid] = useState<boolean | undefined>(undefined);
  const [value, setValue] = useState(initialValue);

  const validate = (val: string) => {
    setIsValid(undefined);

    const isValid = isTouched && val !== "";
    setIsValid(isValid);
  };

  const markTouched = () => {
    setIsTouched(true);
  };

  useEffect(() => {
    validate(value);
  }, [value]);

  return (
    <IonInput
      placeholder={placeholder}
      value={value}
      className={`${isValid && "ion-valid"} ${
        isValid === false && "ion-invalid"
      } ${isTouched && "ion-touched"}`}
      type="text"
      fill="solid"
      label={label}
      labelPlacement="floating"
      helperText="Enter a non-empty value."
      errorText="Field is required"
      onIonChange={(event) => {
        const inputValue = event.detail.value!;
        setValue(inputValue);
        // Call the validate method
        validate(inputValue);
        // Call the onChange prop
        if (onChange) {
          onChange(inputValue);
        }
      }}
      onIonBlur={() => markTouched()}
    />
  );
}

export default RequiredTextInput;

HOwever, if I click the submit button without touching any of the controls, my error message for this particular control doesn't display. Not quite sure the proper way to wire this up between Ionic and the React hook form facility.

1
  • I can recommend the formik package. This manages thinks like focusing and blurring an input element together with input validation. You can define simple validation schemes using Yup. Hope that helps you!
    – Fatorice
    Commented Nov 23, 2023 at 12:00

1 Answer 1

2

It looks like you are using a combination of React Hook Form (RHF) and Ionic to build a form with validation. However, there seems to be a slight misunderstanding in the integration between RHF and the custom input component (RequiredTextInput). Let me guide you on how to properly integrate them.

Firstly, in your RequiredTextInput component, you have an onIonBlur event that marks the input as touched. However, for React Hook Form, you typically don't need to manually track touched status. RHF automatically handles this when the Controller component renders.

Here's a revised version of your RequiredTextInput component:

import { IonInput, IonLabel, IonItem, IonText } from "@ionic/react";
import { useEffect } from "react";
import { useController, FieldValues } from "react-hook-form";

interface RequiredTextInputProps {
  label: string;
  placeholder: string;
  name: string;
  control: any; // Type 'any' for simplicity, you can replace with specific type
}

function RequiredTextInput({
  label,
  placeholder,
  name,
  control,
}: RequiredTextInputProps) {
  const {
    field: { onChange, onBlur, value },
    fieldState: { invalid, error },
  } = useController({
    name,
    control,
    rules: { required: "Field is required" },
  });

  return (
    <IonItem>
      <IonLabel position="floating">{label}</IonLabel>
      <IonInput
        placeholder={placeholder}
        value={value}
        onIonChange={(e) => onChange(e.detail.value)}
        onIonBlur={onBlur}
        className={`${invalid && "ion-invalid"} ${error && "ion-touched"}`}
      />
      {invalid && (
        <IonText color="danger">
          <small>{error?.message}</small>
        </IonText>
      )}
    </IonItem>
  );
}

export default RequiredTextInput;

Now, in your form, use the Controller component to wrap your custom input:

import { useForm, Controller } from "react-hook-form";
import RequiredTextInput from "./RequiredTextInput";

function YourForm() {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const handleSaveAndContinue = (data) => {
    // Handle form submission
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(handleSaveAndContinue)}>
      <Controller
        name="firstName"
        control={control}
        render={({ field }) => (
          <RequiredTextInput
            label="First Name"
            placeholder="Enter your first name"
            name="firstName"
            control={control}
          />
        )}
      />
      {/* Add more fields as needed */}
      <button type="submit">Save and Continue</button>
    </form>
  );
}

export default YourForm;

This way, React Hook Form will manage the state, validation, and errors for your form fields. The RequiredTextInput component will receive the necessary props from the Controller, and you won't need to manually track touched status or validation.

Please adjust the types in the code according to your project's requirements.

Not the answer you're looking for? Browse other questions tagged or ask your own question.