We don't need local state or store with React Hook Forms

We don't need local state or store with React Hook Forms

Story Behind the Blog

Until recently, we managed all our form data using MobX. However, after introducing React Hook Form, form validation became much simpler. We realized that there was no need to save the form data in multiple places since React Hook Form manages its own state. This change eliminated a ton of boilerplate code for us.

What is React Hook Form?

React Hook Form is a lightweight library that uses React hooks to handle form state and validation. It fits right into the existing React component structure, making it easy to build and manage forms without a lot of extra code.

Why You Don’t Need a Separate State

Managing form state usually means keeping track of local state with the useState hook, Context API, or a state management library like Redux or Mobx. However, React Hook Form takes care of this for you by providing its own context.

Getting Started with React Hook Form

npm install react-hook-form --save

Now, let's create a basic form component using React Hook Form:

import React from 'react';
import { useForm } from 'react-hook-form';

const SimpleForm = () => {
  const { register, handleSubmit, watch, formState: { errors } } = useForm();
  const onSubmit = data => {
    console.log(data)
    // make api call to save the form
    alert(JSON.stringify(data));
  }

  console.log(watch("example")); // watch input value by passing the name of it

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <input {...register("example")} />
      {errors.example && <span>This field is required</span>}

      <button type="submit">Submit</button>
    </form>
  );
};

export default SimpleForm;

In this example, the useForm hook provides several methods and properties to manage the form:

  • register: A function to register input fields.

  • handleSubmit: A function to handle form submission.

  • watch: A function to watch the value of a specific input field.

  • formState: An object containing the form's state, including validation errors.

Handling Validation

React Hook Form makes form validation simple and declarative. You can specify validation rules directly within the register function. Here’s an example:

import React from 'react';
import { useForm } from 'react-hook-form';

const ValidationForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = data => {
    console.log(data)
    // make api call to save the form
    alert(JSON.stringify(data));
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <input {...register("username", { required: true })} />
      {errors.username && <span>This field is required</span>}

      <input {...register("email", { 
        required: "Email is required", 
        pattern: {
          value: /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,
          message: "Invalid email address"
        }
      })} />
      {errors.email && <span>{errors.email.message}</span>}
       <button type="submit">Submit</button>
    </form>
  );
};

export default ValidationForm;

In this example, we've added validation rules for the username and email fields. If the validation rules are not met, error messages will be displayed. We can simplify adding validation even more by using popular libraries like Zod or Yup.

here is how we can simplify this further by using zod -

import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// Define your schema with zod
const schema = z.object({
  username: z.string().nonempty({ message: "This field is required" }),
  email: z.string().nonempty({ message: "Email is required" }).email({ message: "Invalid email address" }),
});

const ValidationFormWithZod = () => {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = data => {
    console.log(data)
    // make api call to save the form
    alert(JSON.stringify(data));
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <div>
        <input {...register("username")} />
        {errors.username && <span>{errors.username.message}</span>}
      </div>

      <div>
        <input {...register("email")} />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
};

export default ValidationFormWithZod;

Eliminating the Need for Separate State

By leveraging the useForm hook, React Hook Form manages the form state internally. This means you don't need to use useState to manage input values or validation errors. React Hook Form takes care of updating the form state as users interact with the form, making your code cleaner and more maintainable.

Consider a traditional approach with useState:

import React, { useState } from 'react';

const TraditionalForm = () => {
  const [formData, setFormData] = useState({ username: '', email: '' });
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = {};
    if (!formData.username) newErrors.username = "Username is required";
    if (!formData.email) {
      newErrors.email = "Email is required";
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = "Email is invalid";
    }
    setErrors(newErrors);
    if (Object.keys(newErrors).length === 0) {
        console.log(formData);
        // make api call to save the form
        alert(JSON.stringify(data));
    }
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <input 
        name="username"
        value={formData.username}
        onChange={handleChange}
      />
      {errors.username && <span>{errors.username}</span>}

      <input 
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      {errors.email && <span>{errors.email}</span>}

       <button type="submit">Submit</button>
    </form>
  );
};

export default TraditionalForm;

In this traditional approach, you manage form state and validation manually using useState. This can quickly become cumbersome as the form complexity grows. React Hook Form simplifies this by handling the form state internally, reducing the amount of boilerplate code you need to write.

Thanks for reading !

Did you find this article valuable?

Support Anoop Jadhav by becoming a sponsor. Any amount is appreciated!