Coding, Insights, and Digital Discoveries 👩🏻‍💻

From Stay-at-Home Mom to a Developer After 50

Published on

Master React Hook Form - useFieldArray for Dynamic Forms Creation

react-hook-form-useFieldArray

The useFieldArray hook in React Hook Form is a powerful tool designed for managing dynamic forms where the number of input fields can vary. It simplifies the process of adding, removing, and reordering fields while ensuring optimal performance and accessibility. I will explain the key aspects of useFieldArray with code examples to make it easier to understand.

Core Concepts

At its heart, useFieldArray is built to handle arrays of fields within your form. It's particularly useful for scenarios like:

  • Shopping carts where users can add multiple items.
  • Forms for collecting data on team members, list of skills, or other repeating sets of information.

Key Benefits:

  • Performance: useFieldArray optimizes re-renders, ensuring a smooth user experience even with many dynamic fields.
  • 🌐 Accessibility: It aids in proper focus management, making forms more user-friendly, especially for people using assistive technologies.
  • Validation: You can apply validation rules to the entire field array or individual fields within it.

Using useFieldArray

Let's break down how to use useFieldArray effectively.

1. Setup

Begin by importing necessary components and setting up your form with useForm:

import React from "react";
import { useForm, useFieldArray } from "react-hook-form";

function App() {
  const { register, control, handleSubmit } = useForm();
  const { fields, append, remove } = useFieldArray({
    control, // control from useForm
    name: "items", // unique name for your field array
  });

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Field array rendering goes here */}
      <button type="submit">Submit</button>
    </form>
  );
}

2. Rendering Fields

The fields array returned by useFieldArray is crucial for rendering your form inputs. You'll use this array to map over and generate your input fields:

<ul>
  {fields.map((field, index) => (
    <li key={field.id}> //include key with field's id
      <input {...register(`items.${index}.name`)} /> 
      <button type="button" onClick={() => remove(index)}>
        Delete
      </button>
    </li>
  ))}
</ul>
Important Points:
  • key={field.id}: Always use the unique field.id provided by useFieldArray for the key prop. This is crucial for React to manage re-renders properly and prevent data loss when fields are manipulated.
  • register('items.${index}.name'): The register function is used to associate each input with the form's data. The naming convention (items.${index}.name) ensures that data is correctly structured within the form's values. Here the items is the unique name for this field array.

3. Adding Fields

Use the append function to dynamically add more fields to your array:

<button type="button" onClick={() => append({ name: "" })}>
  Add Item
</button>

4. Removing Fields

The remove function allows for the deletion of fields:

<button type="button" onClick={() => remove(index)}> 
  Delete 
</button>

More Advanced Features

These following examples illustrate how you can use the advanced methods provided by useFieldArray to create more complex and interactive dynamic forms. The ability to add, remove, reorder, update, and replace fields within a form makes useFieldArray a powerful tool in the React Hook Form library.

  • prepend(obj) 🍎: This method adds a new field object to the beginning of the field array. Remember to provide default values for all the input fields within the object:

    <button type="button" onClick={() => prepend({ name: "Apple", quantity: 1 })}>
      Prepend Item 🍎
    </button>
    
  • insert(index, obj) 🍍: Inserts a new field object at the specified index. Like append and prepend, you need to provide an object with default values for all fields:

    <button type="button" onClick={() => insert(2, { name: "Pineapple", quantity: 1 })}>
      Insert at Index 2 🍍
    </button>
    
  • swap(indexA, indexB) 🔄: Swaps the positions of the field objects at the given indices:

    <button type="button" onClick={() => swap(0, 2)}>
      Swap Items 0 and 2 🔄
    </button>
    
  • move(fromIndex, toIndex) ➡️: Moves a field object from one index to another:

    <button type="button" onClick={() => move(1, 3)}>
      Move Item from 1 to 3 ➡️
    </button>
    
  • update(index, obj) ✏️: This method is for updating the values of an existing field object at a particular index. Provide a complete object with the updated values.

    <button type="button" onClick={() => update(0, { name: "Updated Banana", quantity: 5 })}>
      Update Item 0 ✏️
    </button>
    
  • replace(newArray) 📦: This replaces the entire existing field array with a new array of objects:

    const newItems = [
      { name: "Watermelon", quantity: 2 },
      { name: "Grapes", quantity: 1 },
    ];
    
    <button type="button" onClick={() => replace(newItems)}>
      Replace Entire Array 📦
    </button>
    
Key Points 🔑
  • The objects passed to these functions must include default values for all the fields in your field array. For example, if your field array has 'name' and 'quantity' fields, your object should look like: { name: 'itemName', quantity: 1 }.

  • These functions directly modify the fields array returned by useFieldArray. React will re-render your component automatically to reflect these changes, making your form dynamic and interactive.

Validation

You can use the rules prop in useFieldArray to apply validation rules to an entire field array, not just individual fields within the array.

Here's an example and explanation:

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

function MyForm() {
  const { 
    register,
    control,
    handleSubmit,
    formState: { errors } 
  } = useForm();

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'items',
    rules: { minLength: 2, message: "You need at least two items." }, 
  });

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <ul>
        {fields.map((field, index) => (
          <li key={field.id}>
            <input {...register(`items.${index}.name`)} /> 
            <button type="button" onClick={() => remove(index)}>
              Delete
            </button>
          </li>
        ))}
      </ul>

      {errors.items && errors.items.root && (
        <p className="error">{errors.items.root.message}</p>
      )} 

      <button type="button" onClick={() => append({ name: "" })}>
        Add Item
      </button>

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

Explanation:

  • rules: { minLength: 2 }: This rule within the useFieldArray configuration specifies that the items array must have at least two items. You can add a custom message to the rule using the message key, as shown in the code example.
  • Accessing the error: The formState.errors object will contain information about any validation errors. To access errors specific to a field array, you look at the errors.fieldArrayName.root property. In the example above, you would check errors.items.root. If the minLength rule is violated, errors.items.root will contain an object with a message property.

Important Notes:

  • Built-in Validation: The rules property in useFieldArray is primarily intended for built-in validation rules provided by React Hook Form (like required, minLength, maxLength, etc.).
  • Custom Validation: If you need more complex or custom validation logic for the entire field array, you can use the validate function within the rules object.

Key Considerations

  • 🆔 Unique Names: Input names within a field array must be unique.
  • ⚠️ Avoid Stacked Actions: Refrain from performing multiple useFieldArray actions directly in sequence. Instead, use useEffect for delayed actions to ensure proper rendering.
  • 🔧 Default Values: Always provide complete default values when appending, prepending, inserting, or updating fields.
  • 📝 TypeScript: Pay attention to the type casting of input names and nested field array structures when using TypeScript.

Avoid Stacked Actions:

It's important to avoid calling multiple useFieldArray actions consecutively within the same function or event handler. Doing so can lead to unexpected behavior and incorrect rendering. Instead, you should use the useEffect hook to manage delayed or sequential actions involving useFieldArray.

Here's why this is necessary:

  • React Hook Form manages its state updates internally. When you perform actions like append or remove, React Hook Form needs a render cycle to process these updates and reflect them in the fields array correctly.
  • If you call multiple actions in a row, like append() followed immediately by remove(0), React Hook Form might not have enough time to update its internal state before the second action is performed. This can cause issues where the remove() action targets the wrong index or operates on outdated data.

Example: Using useEffect to Manage Stacked Actions

Let's look at an example where you want to append a new item to the field array and then immediately remove the first item.

import React, { useEffect } from "react";
import { useForm, useFieldArray } from "react-hook-form";

function App() {
  const { register, control, handleSubmit } = useForm();
  const { fields, append, remove } = useFieldArray({
    control,
    name: "items", 
  });

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

  const handleAppendAndRemove = () => {
    append({ name: "New Item" }); 

    // Incorrect: Don't remove immediately after appending! 
    // remove(0); 
  };

  useEffect(() => {
    // Correct: Remove the first item after the component re-renders 
    if (fields.length > 1) {
      remove(0); 
    }
  }, [fields, remove]); 

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <ul>
        {fields.map((field, index) => (
          <li key={field.id}>
            <input {...register(`items.${index}.name`)} />
            <button type="button" onClick={() => remove(index)}>
              Delete
            </button>
          </li>
        ))}
      </ul>
      <button type="button" onClick={handleAppendAndRemove}>
        Append and Remove First
      </button>
      <button type="submit">Submit</button>
    </form>
  );
}

Explanation:

  1. handleAppendAndRemove(): This function appends a new item. We avoid calling remove(0) immediately after.
  2. useEffectHook: We use useEffect to delay the removal action.
    • The useEffect hook runs after the component has rendered.
    • The dependency array [fields, remove] ensures that the useEffect will run whenever the fields array or the remove function changes.
  3. Conditional Removal: Inside the useEffect, we check if there are more than one item in the fields array. This is to prevent removing the new item we just appended. If there are multiple items, we use remove(0) to delete the first item.

By using useEffect for delayed actions involving useFieldArray, you can ensure proper rendering and avoid issues caused by stacked actions. This pattern is crucial for maintaining the integrity and predictability of your dynamic forms built with React Hook Form.

Test your understanding

Use this Q&A to assess your understanding of the powerful useFieldArray concept from react-hook-form.

To wrap up, useFieldArray is an invaluable tool for building dynamic and interactive forms with React Hook Form. It streamlines the management of field arrays, provides built-in performance optimizations, and ensures accessibility, making form development efficient and user-friendly.

← See All Posts