Curious Coding Adventures of a Developer

Curious Coding Adventures of a Developer

Large Form Handling in React

Large Form Handling in React

React Hook Form Guide - Intro

image.png

Every Frontend Developer will at some point in their journey write an ungodly sized form and often question their existence for countless hours. Serializing fields, error handling, validation, field interactions and show/hide toggles are the bane of every web developer's career but it doesn't have to be like that.

In this post I'll showcase the advantages of react-hook-form over a no library approach and in a future article will touch on more advanced topics such as Nested Forms, Wizard Forms and integrating with 3rd party libraries.

Let's write a Vanilla (classic) React form for a PetShop website where you can get a nanny for your furry 🐶 friend while you are away.

import { useState } from "react";

const DogForm = () => {
  const [formState, setFormState] = useState({});

  const handleChange = (event) => {
    // common trick to add a value to state with the name of the HTMLElement
    // that triggered the onChange Event, works on most form elements
    setFormState({ ...formState, [event.target.name]: event.target.value });
  };

  const [errors, setErrors] = useState([]);
  const onSubmit = (event) => {
    // prevent location reload on form submit
    event.preventDefault();
    const errors = [];
    // validate each field manually
    if (formState?.name?.length < 6) {
      errors.push("Name should have at least 6 characters");
    }
    if (formState?.weight < 1) {
      errors.push("We don't accept ghost animals 😢");
    }
    // ... and other fields validation
    if (errors.length === 0) {
      console.log(formState);
    } else setErrors(errors);
    // make a POST request to API in real app with formState
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        type="text"
        name="name"
        placeholder="Please type your dog name"
        onChange={handleChange}
      />
      <input
        type="date"
        name="date_of_birth"
        placeholder="Please type date of birth"
        onChange={handleChange}
      />
      <input
        type="number"
        name="weight"
        placeholder="Please type your dog weight"
        onChange={handleChange}
      />
      <label>
        <input type="radio" name="size" value="tiny" onChange={handleChange} />
        Tiny
      </label>
      <label>
        <input
          type="radio"
          name="size"
          value="medium"
          onChange={handleChange}
        />
        Medium
      </label>
      <label>
        <input type="radio" name="size" value="large" onChange={handleChange} />
        Large
      </label>
      <div className="error-section">
        {errors.map((error) => (
          <p>{error}</p>
        ))}
      </div>
      <button type="submit">Add Dog</button>
    </form>
  );
};

export default DogForm;

This works fine for smaller forms but having to add field validation and handling errors manually becomes a huge hassle as forms expand.

Drumrolls please 🥁...

Introducing React Hook Form

I'm aware of the presence of many other form managing libraries but I for one enjoy working with react-hook-form more then others like Formik, redux-form or react-final-form due to it's simple hooks API.

Hooks are great for reusability and asking for no more than the amount of functionality you desire in a certain component and react-hook-form hooks do exactly that.

Let's convert the latter using react-hook-form (Version 7) hooks and remain awed by their efectiveness:

import { useForm } from "react-hook-form";

const DogForm = () => {
  const {
    register,
    formState: { errors },
    handleSubmit
  } = useForm({
    mode: "onChange"
  });

  const onSubmit = (data) => {
    // make a POST request to API in real app with formState
    console.log(data);
  };

  return (
    // wrap our onSubmit in handleSubmit to validate fields
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        // we can use register to register fields and add validation rules
        // it will set the name (and reference) of input from the first arg in function
        {...register("name", {
          required: "Please type your dog name",
          minLength: 6
        })}
        type="text"
        placeholder="Please type your dog name"
      />
      <input
        {...register("date_of_birth")}
        type="date"
        placeholder="Please type date of birth"
      />
      <input
        {...register("weight", {
          required: "We don't accept ghost animals 😢",
          min: 1
        })}
        type="number"
        placeholder="Please type your dog weight"
      />
      <label>
        <input {...register("size")} type="radio" value="tiny" />
        Tiny
      </label>
      <label>
        <input {...register("size")} type="radio" value="medium" />
        Medium
      </label>
      <label>
        <input {...register("size")} type="radio" value="large" />
        Large
      </label>
      {/* select values of errors object and map through them */}
      <div className="error-section">
        {errors && Object.values(errors).map((error) => <p>{error.message}</p>)}
      </div>
      <button type="submit">Add Dog</button>
    </form>
  );
};

export default DogForm;

We achieve the same thing in a much easier to understand manner for any newcomer getting to our project by allowing react-hook-form to manage state and validate fields.

To learn more about useForm hook and many more features check the amazing react-hook-form documentation.

I'll be writing another part of this article in the future taking on advanced react-hook-form tactics for nested forms, wizard forms and using with 3rd party components so stay tuned 🚀.

If you want to check the code in this article here is the CodeSandbox.

I hope you enjoyed this short guide for modern forms in React and would love if you give it a 🦄!

If you want to support my endeavours in the development world check my buymeacoffee https://www.buymeacoffee.com/snappy.guy

 
Share this