how to validate modern forms
user profile avatar
tech girl

Published on • 🕑4 min read

A Better Way to Validate HTML Forms Without Usestate

0likes0

Blog views371

Listen to this blog

Form validation is a crucial aspect of web development, ensuring that users input data correctly before submitting it. Traditionally, developers often manage validation by storing error states in JavaScript and then displaying those errors to the user. While this method works, it can be cumbersome, especially as the complexity of forms grows

If you are a react dev or use other javascript frameworks like me, you often set a custom error state to track whether a field has an error and display the error message to the user. Something like this:

export default function Form() {
  const [error, setError] = useState(null);
  const [email, setEmail] = useState("");
  const emailRegex =  /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
  async function handleSubmit(e) {
    e.preventDefault();
    setError("");
    try {
      // check if the email matches the regex
      if (!emailRegex.test(email)) {
        setError("Please enter a valid email address");
      } else {
        // do something with the email
      }
    } catch (error) {}
  }
  return (
    <form onSubmit={handleSubmit}>
      <div className="px-6">
        <div className="space-y-2 py-2">
          <label className="font-bold " htmlFor="email">
            Enter Your Email
          </label>
          <input
            className="h-10 w-full p-2 border"
            id="email"
            name="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="you@example.com"
            disabled={loading}
            required
            type="email"
          />
        </div>
        <div className="h-5 min-h-5 max-h-5">
          {error ? (
            <p className="text-orange-600 text-sm  ">
              <span>{error}</span>
            </p>
          ) : null}
        </div>
        <button
          className="px-4 bg-blue-500 text-white rounded-md "
          type="submit"
          title="continue">
          Continue
        </button>
      </div>
    </form>
  );
}

The problem with this implementation is that we are unnecessarily using useState to store the error and then render the error in a div. When the div appears, there is a slight layout shift. Furthermore, if you are used to this design, you might find it hard to validate forms when using JavaScript with no frameworks.

One of the major reasons why I found myself using this design is that the native email validation provided by HTML is not enough. For example, setting the input type='email' will indicate that emails ending with numbers are valid as long as they end with something. Thus, something@some.some285 would be a valid email input, although we both know it is not. 

Furthermore, pattern attributes cannot be passed to all HTML inputs, which adds another complexity. I have also experienced a challenge where the pattern attribute does not work on email inputs, although the solution to this is to escape the '/' in the regex pattern.

<form>
  <input type="email" placeholder="enter your email" 
     pattern="^[A-Za-z0- 9._+\-']+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$" 
     required>
  <button>submit</button>
</form>

A Better Solution: Using checkValidity and setCustomValidity

Instead of manually tracking errors, you can leverage the browser’s built-in validation using checkValidity and setCustomValidity. Here’s how it works:

  • checkValidity: This method checks if a form element meets all its validation constraints. It returns true if the element is valid and false if it isn't.

  • setCustomValidity: This method allows you to set a custom error message that will be displayed when the form is invalid. If you pass an empty string, it clears the custom error, making the field valid again.

Here is an example of how you can use customValidity to set your custom error messages:

<body>
    <form style="padding: 5px">
      <label for="email">Email:</label>
      <input
        type="email"
        id="email"
        name="email"
        required
        style="border: 1px solid black" />
      <br />
      <button
        type="submit"
        style="
          background-color: aqua;
          border-radius: 8px;
          padding: 2px 5px;
          margin-top: 5px;
        ">
        Submit
      </button>
    </form>
    <script>
      const emailInput = document.getElementById("email");
      const emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
      emailInput.addEventListener("input", () => {
        if (emailInput.value === "") {
          emailInput.setCustomValidity("Email is required");
        } else if (!emailRegex.test(emailInput.value)) {
          emailInput.setCustomValidity("Please enter a valid email address");
        } else {
          emailInput.setCustomValidity(""); // Clears any custom errors
        }
        emailInput.reportValidity(); // Optionally display the error message immediately
      });
    </script>
  </body>

Benefits of This Approach

  1. Simplified Code: By relying on the browser’s validation mechanisms, you avoid the need to manually track and manage error states. This leads to cleaner and more maintainable code.

  2. Better User Experience: Custom error messages can be displayed directly in the form without the need for additional UI elements. This makes the validation process feel more integrated and user-friendly.

  3. Consistency: Since the browser handles validation, you get consistent behavior across different form elements and input types. This can also allow you to set validation into other fields such as textarea where patterns are not natively supported.

  4. Accessibility: Built-in validation is generally more accessible, as it works with screen readers and other assistive technologies out of the box.

  5. Using State: We can eliminate the need to use useState to track user input and thus make our forms server components with working validation.

You can also be more aggressive and choose to harass your users and make it fun for you of course.

function validate(inputID) {
  const input = document.getElementById(inputID);
  const validityState = input.validity;
  if (validityState.valueMissing) {
    input.setCustomValidity("You gotta fill this out, yo!");
  } else if (validityState.rangeUnderflow) {
    input.setCustomValidity("We need a higher number!");
  } else if (validityState.rangeOverflow) {
    input.setCustomValidity("Thats too high!");
  } else {
    input.setCustomValidity("");
  }
  input.reportValidity();
}

Conclusion

Form validation is an essential part of web development, but it doesn't have to be complicated. By using checkValidity and setCustomValidity, you can leverage the power of HTML5’s built-in validation to simplify your code and improve the user experience. This approach eliminates the need to manually manage error states, making your forms easier to maintain and more reliable.

Next time you’re working on a form, consider using these methods to streamline your validation process and let the browser do the heavy lifting for you.

Like what you see? Share with a Friend

0 Comments

0 Likes

Comments (0)

sort comments

Before you comment please read our community guidelines


Please Login or Register to comment

conversation-starter

This thread is open to discussion

✨ Be the first to comment ✨