eMoosavi
Weekly frontend dev braindumps
Crafting Dynamic Forms with React Hook Form and Yup
React Hooks

Crafting Dynamic Forms with React Hook Form and Yup

Elevate your form handling in React with React Hook Form and Yup for robust validation

Jul 04, 2024 - 00:195 min read

Forms are the cornerstone of user interactivity on the web, but handling them effectively can be a challenging task, especially when considering validation, state management, and performance.

React Hook Form is a powerful library that simplifies form handling in React applications by leveraging hooks and the Context API. In conjunction with Yup for validation, it can significantly streamline the process of building and maintaining dynamic forms.

Getting Started

First things first, let's set up our project. Ensure you have Node.js installed, and create a new React project:

npx create-react-app dynamic-forms
cd dynamic-forms

Install React Hook Form and Yup:

npm install react-hook-form yup @hookform/resolvers

Setting Up Your Form

Let's start by creating a basic form utilizing React Hook Form. Create a Form.js file in your src directory:

import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";

const schema = Yup.object().shape({
  name: Yup.string().required("Name is required"),
  email: Yup.string().email("Invalid email format").required("Email is required"),
  age: Yup.number().positive("Age must be positive").integer("Age must be an integer"),
});

function Form() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Name:</label>
        <input {...register("name")} />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>Age:</label>
        <input {...register("age")} />
        {errors.age && <p>{errors.age.message}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

export default Form;

In this example, we create a form that collects a user's name, email, and age. We use Yup to define our validation schema, which React Hook Form integrates seamlessly into using its yupResolver function.

Conditional Fields and Dynamic Validation

One of the strengths of React Hook Form is its flexibility to manage complex forms, including conditional fields. Suppose we want to show a 'Phone Number' field only if the user chooses to provide it. Additionally, we'll validate this field only if it's filled.

Let's modify our form to include a checkbox that toggles the 'Phone Number' field:

import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";

const schema = Yup.object().shape({
  name: Yup.string().required("Name is required"),
  email: Yup.string().email("Invalid email format").required("Email is required"),
  age: Yup.number().positive("Age must be positive").integer("Age must be an integer"),
  phone: Yup.string().when('providePhone', {
    is: true,
    then: Yup.string().required("Phone number is required").matches(/^\d{10}$/, "Phone number must be 10 digits")
  })
});

function Form() {
  const [showPhone, setShowPhone] = useState(false);
  const { register, handleSubmit, control, formState: { errors } } = useForm({
    resolver: yupResolver(schema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Name:</label>
        <input {...register("name")} />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input {...register("email")} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>Age:</label>
        <input {...register("age")} />
        {errors.age && <p>{errors.age.message}</p>}
      </div>
      <div>
        <label>
          <input type="checkbox" onChange={() => setShowPhone(!showPhone)} /> Provide phone number
        </label>
      </div>
      {showPhone && (
        <div>
          <label>Phone:</label>
          <input {...register("phone")} />
          {errors.phone && <p>{errors.phone.message}</p>}
        </div>
      )}
      <button type="submit">Submit</button>
    </form>
  );
}

export default Form;

Here, we used the when function from Yup to conditionally apply validation rules based on the providePhone state. The Controller component from React Hook Form can help integrate more complex state or components into your forms.

Advanced Usage: Nested Forms

Another common requirement is nested forms or repeating fields. React Hook Form handles this elegantly using array fields. Let’s create a form to collect multiple addresses:

import React from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";

const schema = Yup.object().shape({
  addresses: Yup.array().of(
    Yup.object().shape({
      street: Yup.string().required("Street is required"),
      city: Yup.string().required("City is required"),
    })
  ).required("At least one address is required"),
});

function AddressForm() {
  const { register, control, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema),
    defaultValues: { addresses: [{ street: "", city: "" }] },
  });
  const { fields, append, remove } = useFieldArray({
    control,
    name: "addresses",
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((item, index) => (
        <div key={index}>
          <label>Street:</label>
          <input {...register(`addresses.${index}.street`)} />
          {errors.addresses && errors.addresses[index]?.street && <p>{errors.addresses[index].street.message}</p>}

          <label>City:</label>
          <input {...register(`addresses.${index}.city`)} />
          {errors.addresses && errors.addresses[index]?.city && <p>{errors.addresses[index}.city.message}</p>}

          <button type="button" onClick={() => remove(index)}>Remove</button>
        </div>
      ))}
      <button type="button" onClick={() => append({ street: "", city: "" })}>Add Address</button>
      <button type="submit">Submit</button>
    </form>
  );
}

export default AddressForm;

In this example, the useFieldArray hook is used to dynamically manage an array of address fields. Each address is validated with Yup schemas, ensuring proper data integrity.

Performance Considerations

React Hook Form is optimized for performance, as it minimizes re-renders by isolating field updates and leveraging uncontrolled component principles. Here are a few tips to ensure optimal performance:

  1. Use defaultValues: Define default values for fields using the defaultValues property in the useForm hook.
  2. Isolate Re-Renders: Use Controller only when necessary, which lets you synchronize a controlled input.
  3. Avoid Unnecessary State: Keep form state minimal and within the useForm hook context to avoid unnecessary re-renders.

For example, using Controller can help manage more complex form components without unnecessary re-renders:

import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "@material-ui/core";

function Form() {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="username"
        control={control}
        defaultValue=""
        render={({ field }) => <TextField {...field} label="Username" />}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export default Form;

Conclusion

React Hook Form combined with Yup offers a powerful and flexible solution for handling forms in React. Whether dealing with simple forms, conditional fields, or complex nested structures, these libraries together provide scalability and maintainability to your form logic.

Leveraging these tools can enhance the user experience by providing immediate validation feedback and robust error handling while maintaining optimal performance. Give them a try in your next React project to create dynamic, user-friendly forms effortlessly.

Article tags
reactreact-hook-formvalidationyupforms
Previous article

React Components

Unleashing the Power of React Portals for Flexible UI Components

Next article

Frontend Architecture

Unlocking the Full Potential of ES Module Import Statements for Advanced Code Splitting