Published:

React - Dynamic Form Example with React Hook Form

Built with React 16.13.1 and React Hook Form 6.8.6

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.

React Hook Form is a relatively new library for working with forms in React using React Hooks, I just stumbled across it recently and will be using it for my React projects going forward, 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.

Styling of the example is all done with Bootstrap 4.5 CSS, for more info see https://getbootstrap.com/docs/4.5/getting-started/introduction/.

Here it is in action: (See on StackBlitz at https://stackblitz.com/edit/react-hook-form-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.

Form validation rules are defined with the Yup schema validation library and passed to the useForm() function of the React Hook Form library.

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

A watcher is created on the numberOfTickets select field to provide access to the current number of tickets and to trigger a for re-render when the number of tickets is changed. The ticketNumbers() function returns an array of ticket indexes (e.g. [0,1,2,3...]) based on the number of tickets selected and is used in the component template to render the dynamic form fields for each ticket.

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

import React from 'react';
import { useForm } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers';
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')
            })
        )
    });

    // functions to build form returned by useForm() hook
    const { register, handleSubmit, reset, errors, watch } = useForm({
        resolver: yupResolver(validationSchema)
    });

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

    // return array of ticket indexes for rendering dynamic forms in the template
    function ticketNumbers() {
        return [...Array(parseInt(watchNumberOfTickets || 0)).keys()];
    }

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

    return (
        <form onSubmit={handleSubmit(onSubmit)} onReset={reset}>
            <div className="card m-3">
                <h5 className="card-header">React Dynamic Form Example with React Hook Form</h5>
                <div className="card-body border-bottom">
                    <div className="form-row">
                        <div className="form-group">
                            <label>Number of Tickets</label>
                            <select name="numberOfTickets" ref={register} 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>
                {ticketNumbers().map(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`} ref={register} 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`} ref={register} 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 className="btn btn-secondary mr-1" type="reset">Reset</button>
                </div>
            </div>
        </form>
    )
}

export { App };

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.