Published:

React + Formik Dynamic Form Example

Built with React 16.13.1 and Formik 2.1.5

Other versions available:

This is a quick example of how to build a dynamic form with validation in React with Formik. 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-formik-dynamic-form-example)


React Dynamic Form App Component

The app component contains the example dynamic form built with the <Formik /> component. The initial values of each field are set in the initialValues property. Validation rules and error messages are set in the validationSchema property. The onSubmit function gets called when the form is submitted and valid. The html and jsx markup for the form is set in callback function contained within the <Formik>...</Formik> component tag.

The dynamic form contains two top level properties:

  • numberOfTickets stores the number of tickets selected and is bound to the select input field.
  • tickets holds an array of ticket objects for storing ticket holder details. Each ticket contains a name and email property which are bound to the dynamically created child form fields.

The onChangeTickets() method updates the tickets array when the number of tickets selected is changed, and updates the Formik form state by calling setValues() to trigger a re-render.

import React from 'react';
import { Formik, Form, Field, FieldArray, ErrorMessage } from 'formik';
import * as Yup from 'yup';

function App() {
    const initialValues = {
        numberOfTickets: '',
        tickets: []
    };

    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')
            })
        )
    });

    function onChangeTickets(e, field, values, setValues) {
        // update dynamic form
        const tickets = [...values.tickets];
        const numberOfTickets = e.target.value || 0;
        const previousNumber = parseInt(field.value || '0');
        if (previousNumber < numberOfTickets) {
            for (let i = previousNumber; i < numberOfTickets; i++) {
                tickets.push({ name: '', email: '' });
            }
        } else {
            for (let i = previousNumber; i >= numberOfTickets; i--) {
                tickets.splice(i, 1);
            }
        }
        setValues({ ...values, tickets });

        // call formik onChange method
        field.onChange(e);
    }

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

    return (
        <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit}>
            {({ errors, values, touched, setValues }) => (
                <Form>
                    <div className="card m-3">
                        <h5 className="card-header">React + Formik Dynamic Form Example</h5>
                        <div className="card-body border-bottom">
                            <div className="form-row">
                                <div className="form-group">
                                    <label>Number of Tickets</label>
                                    <Field name="numberOfTickets">
                                    {({ field }) => (
                                        <select {...field} className={'form-control' + (errors.numberOfTickets && touched.numberOfTickets ? ' is-invalid' : '')} onChange={e => onChangeTickets(e, field, values, setValues)}>
                                            <option value=""></option>
                                            {[1,2,3,4,5,6,7,8,9,10].map(i => 
                                                <option key={i} value={i}>{i}</option>
                                            )}
                                        </select>
                                    )}
                                    </Field>
                                    <ErrorMessage name="numberOfTickets" component="div" className="invalid-feedback" />
                                </div>
                            </div>
                        </div>
                        <FieldArray name="tickets">
                        {() => (values.tickets.map((ticket, i) => {
                            const ticketErrors = errors.tickets?.length && errors.tickets[i] || {};
                            const ticketTouched = touched.tickets?.length && touched.tickets[i] || {};
                            return (
                                <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>
                                                <Field name={`tickets.${i}.name`} type="text" className={'form-control' + (ticketErrors.name && ticketTouched.name ? ' is-invalid' : '' )} />
                                                <ErrorMessage name={`tickets.${i}.name`} component="div" className="invalid-feedback" />
                                            </div>
                                            <div className="form-group col-6">
                                                <label>Email</label>
                                                <Field name={`tickets.${i}.email`} type="text" className={'form-control' + (ticketErrors.email && ticketTouched.email ? ' is-invalid' : '' )} />
                                                <ErrorMessage name={`tickets.${i}.email`} component="div" className="invalid-feedback" />
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            );
                        }))}
                        </FieldArray>
                        <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>
            )}
        </Formik>
    )
}

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.