Published: October 05 2021

React Hook Form 7 - Dynamic Form Example with useFieldArray

Built with React 17.0.2 and React Hook Form 7.15.3

Other versions available:

This is a quick example of how to build a dynamic form with validation in React with the React Hook Form library v7.

React Hook Form is a library for working with forms in React using React Hooks, I stumbled across it about a year ago and have been using it in my React and Next.js projects since then, I think it's easier to use than the other options available and requires less code. For more info see https://react-hook-form.com.

The example form allows selecting the number of tickets to purchase and then entering the name and email of the person each ticket is for, both fields are required and the email field must contain a valid email address. The "Buy Tickets" button simply displays the form values in an alert popup if the form is valid, and the "Reset" button resets the form back to it's initial state including the removal of all ticket name & email fields.

Code on GitHub

The example project is available on GitHub at https://github.com/cornflourblue/react-hook-form-7-dynamic-form-example.

Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/react-hook-form-7-dynamic-form-example)


App Component with Dynamic React Hook Form

The example app component contains all the code for the dynamic form built with React Hook Form 7.

Form validation rules are defined with the Yup schema validation library and passed to the React Hook Form useForm() function, for more info on Yup see https://github.com/jquense/yup.

useForm

The useForm() hook function returns an object with methods and props for working with a form including registering inputs, handling form submit, resetting the form, displaying errors and more, for a complete list see https://react-hook-form.com/api/useform.

useFieldArray

The useFieldArray() hook function returns a dynamic array of fields with the name 'tickets', along with methods to append() and remove() fields from the array. For more info on React Hook Form field arrays see https://react-hook-form.com/api/usefieldarray.

A watcher is created on the numberOfTickets select field to provide access to the current number of tickets and to trigger a useEffect() hook when the number of tickets is changed. The useEffect() hook updates the 'tickets' field array which triggers a re-render of the component to display the new number of ticket fields.

The onSubmit() method is called when the form is valid and submitted, and simply displays the form data in a javascript alert.

The returned JSX template contains the form with all of the input fields and validation messages, including a select list for the numberOfTickets field, and a pair of text inputs for each ticket in the fields dynamic field array. Form inputs are registered with the React Hook Form by calling the register() function with the field name from each input element (e.g. {...register('numberOfTickets')}).

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

function App() {
    // form validation rules 
    const validationSchema = Yup.object().shape({
        numberOfTickets: Yup.string()
            .required('Number of tickets is required'),
        tickets: Yup.array().of(
            Yup.object().shape({
                name: Yup.string()
                    .required('Name is required'),
                email: Yup.string()
                    .email('Email is Invalid')
                    .required('Email is required')
            })
        )
    });
    const formOptions = { resolver: yupResolver(validationSchema) };

    // functions to build form returned by useForm() and useFieldArray() hooks
    const { register, control, handleSubmit, reset, formState, watch } = useForm(formOptions);
    const { errors } = formState;
    const { fields, append, remove } = useFieldArray({ name: 'tickets', control });

    // watch to enable re-render when ticket number is changed
    const numberOfTickets = watch('numberOfTickets');

    useEffect(() => {
        // update field array when ticket number changed
        const newVal = parseInt(numberOfTickets || 0);
        const oldVal = fields.length;
        if (newVal > oldVal) {
            // append tickets to field array
            for (let i = oldVal; i < newVal; i++) {
                append({ name: '', email: '' });
            }
        } else {
            // remove tickets from field array
            for (let i = oldVal; i > newVal; i--) {
                remove(i - 1);
            }
        }
    }, [numberOfTickets]);

    function onSubmit(data) {
        // display form data on success
        alert('SUCCESS!! :-)\n\n' + JSON.stringify(data, null, 4));
    }

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <div className="card m-3">
                <h5 className="card-header">React Hook Form 7 - Dynamic Form Example</h5>
                <div className="card-body border-bottom">
                    <div className="form-row">
                        <div className="form-group">
                            <label>Number of Tickets</label>
                            <select name="numberOfTickets" {...register('numberOfTickets')} className={`form-control ${errors.numberOfTickets ? 'is-invalid' : ''}`}>
                                {['', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i =>
                                    <option key={i} value={i}>{i}</option>
                                )}
                            </select>
                            <div className="invalid-feedback">{errors.numberOfTickets?.message}</div>
                        </div>
                    </div>
                </div>
                {fields.map((item, i) => (
                    <div key={i} className="list-group list-group-flush">
                        <div className="list-group-item">
                            <h5 className="card-title">Ticket {i + 1}</h5>
                            <div className="form-row">
                                <div className="form-group col-6">
                                    <label>Name</label>
                                    <input name={`tickets[${i}]name`} {...register(`tickets.${i}.name`)} type="text" className={`form-control ${errors.tickets?.[i]?.name ? 'is-invalid' : ''}`} />
                                    <div className="invalid-feedback">{errors.tickets?.[i]?.name?.message}</div>
                                </div>
                                <div className="form-group col-6">
                                    <label>Email</label>
                                    <input name={`tickets[${i}]email`} {...register(`tickets.${i}.email`)} type="text" className={`form-control ${errors.tickets?.[i]?.email ? 'is-invalid' : ''}`} />
                                    <div className="invalid-feedback">{errors.tickets?.[i]?.email?.message}</div>
                                </div>
                            </div>
                        </div>
                    </div>
                ))}
                <div className="card-footer text-center border-top-0">
                    <button type="submit" className="btn btn-primary mr-1">
                        Buy Tickets
                    </button>
                    <button onClick={() => reset()} type="button" className="btn btn-secondary mr-1">Reset</button>
                </div>
            </div>
        </form>
    )
}

export { App };

 


Need Some React Hook Form Help?

Search fiverr for freelance React Hook Form developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by