Creating a Custom useForm Hook in React for Dynamic Form Validation

Managing form state and validation in React can often become cumbersome, especially when dealing with complex forms and nested fields. To simplify this process, creating a custom useForm hook can be incredibly beneficial. In this article, we’ll walk th…


This content originally appeared on DEV Community and was authored by Sumit Walmiki

Managing form state and validation in React can often become cumbersome, especially when dealing with complex forms and nested fields. To simplify this process, creating a custom useForm hook can be incredibly beneficial. In this article, we'll walk through the creation of a useForm hook that handles validation, form state management, and error handling in a reusable and dynamic manner.

The useForm Hook
Let's start by defining the useForm hook. This hook will manage the form's state, handle changes, reset the form, and validate fields based on the rules passed to it.

import { useState } from "react";
import validate from "../validate";

const useForm = (
  initialState,
  validationTypes,
  shouldValidateFieldCallback,
  getFieldDisplayName
) => {
  const [formData, setFormData] = useState(initialState);
  const [errors, setErrors] = useState({});
  const [showErrors, setShowErrors] = useState(false);

  const onHandleChange = (newFormData) => {
    setFormData(newFormData);
  };

  const onHandleReset = () => {
    setFormData(initialState);
    setErrors({});
    setShowErrors(false);
  };

  const shouldValidateField = (name) => {
    if (shouldValidateFieldCallback) {
      return shouldValidateFieldCallback(name, formData);
    }
    return true; // Default behavior: always validate if no callback provided
  };

  const validateAll = (currentFormData = formData) => {
    let allValid = true;
    const newErrors = {};

    const traverseFormData = (data) => {
      for (const key in data) {
        if (Object.prototype.hasOwnProperty.call(data, key)) {
          const value = data[key];
          const fieldName = key;

          if (typeof value === "object" && value !== null && !Array.isArray(value)) {
            traverseFormData(value);
          } else if (shouldValidateField(fieldName)) {
            const validationType = validationTypes?.[fieldName];
            if (validationType) {
              const displayName = getFieldDisplayName(fieldName);
              const errorElement = validate(value, validationType, displayName);
              if (errorElement) {
                allValid = false;
                newErrors[fieldName] = errorElement;
              }
            }
          }
        }
      }
    };

    traverseFormData(currentFormData);

    setErrors(newErrors);
    return allValid;
  };

  const onHandleSubmit = (callback) => (e) => {
    e.preventDefault();
    setShowErrors(true);
    if (validateAll()) {
      callback();
    }
  };

  return {
    formData,
    errors,
    showErrors,
    onHandleChange,
    onHandleSubmit,
    onHandleReset,
  };
};

export default useForm;

Explanation:
Initial State Management: We start by initializing the form state and errors using the useState hook.
Change Handling: onHandleChange updates the form state based on user input.
Reset Handling: onHandleReset resets the form state to its initial values and clears errors.

Validation: validateAll traverses the form data, checks validation rules, and sets error messages if any validation fails.
Submission Handling: onHandleSubmit triggers validation and, if successful, executes the provided callback function.
The validate Function
The validate function is responsible for performing the actual validation checks based on the rules specified.

import React from "react";
import { capitalize } from "lodash";
import { constant } from "../constants/constant";

const validate = (value, validationType, fieldName) => {
  if (!validationType) {
    return null; // No validation type specified
  }

  const validations = validationType.split("|");
  let errorMessage = null;

  // Patterns
  const emailPattern = constant.REGEX.BASICEMAILPATTERN;
  const alphaPattern = constant.REGEX.APLHAONLYPATTERN;

  for (const type of validations) {
    const [vType, param] = type.split(":");

    switch (vType) {
      case "required":
        if (value === "" || value === null || value === undefined) {
          errorMessage = `${capitalize(fieldName)} field is required.`;
        }
        break;
      case "email":
        if (value && !emailPattern.test(value)) {
          errorMessage = `${capitalize(fieldName)} must be a valid email address.`;
        }
        break;
      case "min":
        if (value.length < parseInt(param)) {
          errorMessage = `${capitalize(fieldName)} must be at least ${param} characters.`;
        }
        break;
      case "alphaOnly":
        if (value && !alphaPattern.test(value)) {
          errorMessage = `${capitalize(fieldName)} field must contain only alphabetic characters.`;
        }
        break;
      default:
        break;
    }
    if (errorMessage) {
      break;
    }
  }

  return errorMessage ? <div className="text-danger">{errorMessage}</div> : null;
};

export default validate;

Usage Example
Here's how you can use the useForm hook in a form component:

import React from "react";
import useForm from "./useForm"; // Adjust the import path as needed

const MyFormComponent = () => {
  const initialState = {
    UserID: 0,
    UserEmail: '',
    FirstName: '',
    LastName: '',
    LicencesData: {
      LicenseType: null,
      EnterpriseLicense: null,
      IsProTrial: null,
      CreditsBalance: null,
      EAlertCreditsAvailable: null,
      StartAt: null,
      EndAt: null,
    },
  };

  const validationTypes = {
    UserEmail: "required|email",
    FirstName: "required|alphaOnly",
    LastName: "required|alphaOnly",
    "LicencesData.LicenseType": "required",
    "LicencesData.StartAt": "required",
    "LicencesData.EndAt": "required",
  };

  const shouldValidateFieldCallback = (name, formData) => {
    if (name === "Password" && formData.IsAutogeneratePassword) {
      return false;
    }
    if (["LicencesData.StartAt", "LicencesData.EndAt"].includes(name) && formData.LicencesData.LicenseType?.value === 2) {
      return false;
    }
    return true;
  };

  const getFieldDisplayName = (fieldName) => {
    const displayNames = {
      UserEmail: "Email",
      FirstName: "First name",
      LastName: "Last name",
      "LicencesData.LicenseType": "License type",
      "LicencesData.StartAt": "Start date",
      "LicencesData.EndAt": "End date",
    };
    return displayNames[fieldName] || fieldName;
  };

  const { formData, errors, showErrors, onHandleChange, onHandleSubmit, onHandleReset } = useForm(
    initialState,
    validationTypes,
    shouldValidateFieldCallback,
    getFieldDisplayName
  );

  return (
    <form onSubmit={onHandleSubmit(() => console.log("Form submitted successfully!"))}>
      <div>
        <label>Email:</label>
        <input
          type="text"
          name="UserEmail"
          value={formData.UserEmail}
          onChange={(e) => onHandleChange({ ...formData, UserEmail: e.target.value })}
        />
        {showErrors && errors.UserEmail}
      </div>
      <div>
        <label>First Name:</label>
        <input
          type="text"
          name="FirstName"
          value={formData.FirstName}
          onChange={(e) => onHandleChange({ ...formData, FirstName: e.target.value })}
        />
        {showErrors && errors.FirstName}
      </div>
      <div>
        <label>Last Name:</label>
        <input
          type="text"
          name="LastName"
          value={formData.LastName}
          onChange={(e) => onHandleChange({ ...formData, LastName: e.target.value })}
        />
        {showErrors && errors.LastName}
      </div>
      <div>
        <label>License Type:</label>
        <input
          type="text"
          name="LicencesData.LicenseType"
          value={formData.LicencesData.LicenseType || ""}
          onChange={(e) => onHandleChange({ ...formData, LicencesData: { ...formData.LicencesData, LicenseType: e.target.value } })}
        />
        {showErrors && errors["LicencesData.LicenseType"]}
      </div>
      <div>
        <label>Start Date:</label>
        <input
          type="text"
          name="LicencesData.StartAt"
          value={formData.LicencesData.StartAt || ""}
          onChange={(e) => onHandleChange({ ...formData, LicencesData: { ...formData.LicencesData, StartAt: e.target.value } })}
        />
        {showErrors && errors["LicencesData.StartAt"]}
      </div>
      <div>
        <label>End Date:</label>
        <input
          type="text"
          name="LicencesData.EndAt"
          value={formData.LicencesData.EndAt || ""}
          onChange={(e) => onHandleChange({ ...formData, LicencesData: { ...formData.LicencesData, EndAt: e.target.value } })}
        />
        {showErrors && errors["LicencesData.EndAt"]}
      </div>
      <button type="submit">Submit</button>
      <button type="button" onClick={onHandleReset}>Reset</button>
    </form>
  );
};

export default MyFormComponent;

Conclusion
With the custom useForm hook, managing form state and validation in React becomes much more manageable. This hook allows for flexible and dynamic form handling, ensuring that your forms are easy to maintain and extend. By following the patterns outlined in this article, you can create robust form handling logic for any React application.


This content originally appeared on DEV Community and was authored by Sumit Walmiki


Print Share Comment Cite Upload Translate Updates
APA

Sumit Walmiki | Sciencx (2024-06-22T11:14:17+00:00) Creating a Custom useForm Hook in React for Dynamic Form Validation. Retrieved from https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/

MLA
" » Creating a Custom useForm Hook in React for Dynamic Form Validation." Sumit Walmiki | Sciencx - Saturday June 22, 2024, https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/
HARVARD
Sumit Walmiki | Sciencx Saturday June 22, 2024 » Creating a Custom useForm Hook in React for Dynamic Form Validation., viewed ,<https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/>
VANCOUVER
Sumit Walmiki | Sciencx - » Creating a Custom useForm Hook in React for Dynamic Form Validation. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/
CHICAGO
" » Creating a Custom useForm Hook in React for Dynamic Form Validation." Sumit Walmiki | Sciencx - Accessed . https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/
IEEE
" » Creating a Custom useForm Hook in React for Dynamic Form Validation." Sumit Walmiki | Sciencx [Online]. Available: https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/. [Accessed: ]
rf:citation
» Creating a Custom useForm Hook in React for Dynamic Form Validation | Sumit Walmiki | Sciencx | https://www.scien.cx/2024/06/22/creating-a-custom-useform-hook-in-react-for-dynamic-form-validation/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.