eMoosavi
Weekly frontend dev braindumps
Efficiently Handling Form State in React with Immer and React-Hook-Form
React Components

Efficiently Handling Form State in React with Immer and React-Hook-Form

Streamline form management by leveraging Immer and React-Hook-Form

Jul 23, 2024 - 18:145 min read

Managing form state is a critical aspect of web development, and when building complex React applications, it can become quite challenging. In this blog post, we'll explore two powerful tools, Immer and React-Hook-Form, to enhance form state management. By the end of this post, you'll have a solid understanding of how to use these libraries together to create more manageable and efficient forms.

Why Immer and React-Hook-Form?

Immer simplifies the process of immutability in JavaScript. It's particularly useful for managing state in a React application because it allows you to work with immutable data structures in a more intuitive way.

With Immer, you can write code that looks and feels natural, while behind the scenes, Immer ensures that your state remains immutable.

React-Hook-Form (RHF) is a powerful library for managing form state in React. It provides a simple API for integrating forms with minimal re-renders, reducing performance bottlenecks. RHF also supports validations, and it's highly customizable, making it an excellent choice for handling forms in complex applications.

Setting Up the Project

First, let's initialize our project and install the necessary dependencies:

npx create-react-app form-management
cd form-management
npm install immer react-hook-form

Creating a Form Component

Start by creating a Form.js file in the src directory. We'll use RHF to handle the form state and Immer to manage an immutable copy of our form data.

import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import produce from 'immer';

const Form = () => {
  const { control, handleSubmit, setValue } = useForm();
  const onSubmit = data => {
    console.log(data);
  };

  const handleInputChange = (field, value) => {
    setValue(field, value);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="firstName"
        control={control}
        render={({ field }) => (
          <input
            {...field}
            onChange={(e) => handleInputChange("firstName", e.target.value)}
          />
        )}
      />
      <Controller
        name="lastName"
        control={control}
        render={({ field }) => (
          <input
            {...field}
            onChange={(e) => handleInputChange("lastName", e.target.value)}
          />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;

Using Immer to Update Form State

In this section, we'll integrate Immer into our form handling logic to ensure that our state management remains immutable.

import React, { useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import produce from 'immer';

const Form = () => {
  const { control, handleSubmit } = useForm();
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
  });

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

  const handleInputChange = (field, value) => {
    setFormData(produce((draft) => {
      draft[field] = value;
    }));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="firstName"
        control={control}
        render={({ field }) => (
          <input
            {...field}
            value={formData.firstName}
            onChange={(e) => handleInputChange("firstName", e.target.value)}
          />
        )}
      />
      <Controller
        name="lastName"
        control={control}
        render={({ field }) => (
          <input
            {...field}
            value={formData.lastName}
            onChange={(e) => handleInputChange("lastName", e.target.value)}
          />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;

Adding Validation Rules

One of the strengths of React-Hook-Form is its support for validation. Let's add some basic validation rules for our form fields.

import React, { useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import produce from 'immer';

const Form = () => {
  const { control, handleSubmit, formState: { errors } } = useForm();
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
  });

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

  const handleInputChange = (field, value) => {
    setFormData(produce((draft) => {
      draft[field] = value;
    }));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="firstName"
        control={control}
        rules={{ required: "First name is required" }}
        render={({ field }) => (
          <div>
            <input
              {...field}
              value={formData.firstName}
              onChange={(e) => handleInputChange("firstName", e.target.value)}
            />
            {errors.firstName && <span>{errors.firstName.message}</span>}
          </div>
        )}
      />
      <Controller
        name="lastName"
        control={control}
        rules={{ required: "Last name is required" }}
        render={({ field }) => (
          <div>
            <input
              {...field}
              value={formData.lastName}
              onChange={(e) => handleInputChange("lastName", e.target.value)}
            />
            {errors.lastName && <span>{errors.lastName.message}</span>}
          </div>
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;

Now our form has basic validation for the first name and last name fields. If the user submits the form without filling in these fields, an error message will be displayed.

Handling Nested Form Data

In real-world applications, form data often includes nested objects. Let's see how to handle nested fields with Immer and React-Hook-Form.

import React, { useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import produce from 'immer';

const Form = () => {
  const { control, handleSubmit, formState: { errors } } = useForm();
  const [formData, setFormData] = useState({
    user: {
      firstName: '',
      lastName: '',
    }
  });

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

  const handleInputChange = (field, value) => {
    setFormData(produce((draft) => {
      draft.user[field] = value;
    }));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="user.firstName"
        control={control}
        rules={{ required: "First name is required" }}
        render={({ field }) => (
          <div>
            <input
              {...field}
              value={formData.user.firstName}
              onChange={(e) => handleInputChange("firstName", e.target.value)}
            />
            {errors.user?.firstName && <span>{errors.user.firstName.message}</span>}
          </div>
        )}
      />
      <Controller
        name="user.lastName"
        control={control}
        rules={{ required: "Last name is required" }}
        render={({ field }) => (
          <div>
            <input
              {...field}
              value={formData.user.lastName}
              onChange={(e) => handleInputChange("lastName", e.target.value)}
            />
            {errors.user?.lastName && <span>{errors.user.lastName.message}</span>}
          </div>
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

export default Form;

In this example, our form data includes a user object with firstName and lastName fields. By using dotted paths in the name attribute of the Controller component, React-Hook-Form can map these fields to the nested structure correctly.

Conclusion

By integrating Immer and React-Hook-Form, we can create a more intuitive and efficient way to manage form state in React applications. Immer helps us handle immutable state updates seamlessly, while React-Hook-Form simplifies form management and validation with minimal re-renders. Try incorporating these libraries into your next project and experience the power of streamlined form handling.

Article tags
reactimmerreact-hook-formform-handlingstate-management
Previous article

API Integration

Implementing Custom Pagination Logic with React and Tanstack Table

Next article

React Components

Enhancing React Apps with Advanced Animation Patterns