In this post you'll see how to create a React form with dynamic array field using Formik and Yup for object schema validation.
Many a times you need a form where you give user a functionality to add the same set of fields again and again which means giving an input or group of inputs as a dynamic array. For example, suppose you have a student form where you want to give a button to add qualification and whenever user clicks that button a new set of input fields for qualification should be added to the form.
Dynamic React form using Formik's FieldArray
In Formik, which is a third party lightweight React form library, there is a <FieldArray>
component that helps with common array/list manipulations. Using index of the array you can create input groups and do validations.
Formik's FieldArray example
In the example form we'll capture student details so the form fields are Student Name, Student Age and Qualification which is an array having two fields name and year. For the qualification field you want to provide the functionality that student should be able to add more than one qualification.
In this post we'll concentrate more on FieldArray, to get more details about Formik and Yup refer this post- React Form + Formik + Yup Validation Example
import { ErrorMessage, Field, FieldArray, Form, Formik, getIn } from "formik" import * as Yup from 'yup'; import './form.css'; const StudentForm = () => { return ( <Formik initialValues={{ studentName: '', studentAge: '', qualification: [ { name: '', year: '' } ] }} validationSchema={Yup.object({ studentName: Yup.string() .max(20, 'Must be 20 characters or less') .required('Required'), studentAge: Yup.number() .min(18, 'Age must not be less than 18') .max(22, 'Age must not be greater than 22') .required('Required'), qualification: Yup.array().of( Yup.object().shape({ name: Yup.string() .required('Required'), year: Yup.number() .required('Required'), }) ) })} onSubmit={(values, { setSubmitting }) => { setTimeout(() => { console.log(JSON.stringify(values, null, 2)); setSubmitting(false); }, 400); }} > {({ errors, touched, values }) => ( <Form> <h3>Student Form</h3> <div className="row"> <div className="form-group col-sm-4 mb-2" > <label htmlFor="studentName" className="form-label">Student Name</label> <Field type="text" name="studentName" placeholder="Enter studentName" className={'form-control' + (errors.studentName && touched.studentName ? ' is-invalid' : '')} /> <ErrorMessage name="studentName" component="div" className="invalid-feedback" /> </div> <div className="form-group col-sm-4 mb-2" > <label htmlFor="studentAge" className="form-label">Student Age</label> <Field type="text" name="studentAge" placeholder="Enter age" className={'form-control' + (errors.studentAge && touched.studentAge ? ' is-invalid' : '')} /> <ErrorMessage name="studentAge" component="div" className="invalid-feedback" /> </div> </div> <FieldArray name="qualification"> {({ remove, push }) => ( <div> <h4>Add qualifications</h4> <button type="button" className="btn btn-secondary mb-2" onClick={() => push({ name: '', year: '' })} > Add Qualification </button> {values.qualification.length > 0 && values.qualification.map((qual, index) => ( <div className="row" key={index}> <div className="col-sm-4"> <label htmlFor={`qualification.${index}.name`} className="form-label">Name</label> <Field name={`qualification.${index}.name`} placeholder="qualification" type="text" className={'form-control' + (getIn(errors, `qualification.${index}.name`) && getIn(touched, `qualification.${index}.name`) ? ' is-invalid' : '')} /> <ErrorMessage name={`qualification.${index}.name`} component="div" className="invalid-feedback" /> </div> <div className="col-sm-4"> <label htmlFor={`qualification.${index}.year`} className="form-label">Year</label> <Field name={`qualification.${index}.year`} placeholder="year" type="number" className={'form-control' + (getIn(errors, `qualification.${index}.year`) && getIn(touched, `qualification.${index}.year`) ? ' is-invalid' : '')} /> <ErrorMessage name={`qualification.${index}.year`} component="div" className="invalid-feedback" /> </div> <div className="col-sm-1"> <label className="form-label"> </label> <button type="button" className="btn btn-danger btn-as-block" onClick={() => remove(index)} > X </button> </div> </div> ))} </div> )} </FieldArray> <button type="submit" className="btn btn-primary">Submit</button> </Form> )} </Formik> ); } export default StudentForm;
There is also a small CSS code for aligning the delete button.
Form.css
.btn.btn-as-block { display: block; }
Some important points to note here-
- In the example Bootstrap 5 is used for styling. You can import Bootstrap by adding following to the <head>
section of the index.html file.
<link rel="stylesheet" href=https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
- Initial values for the form fields are as empty fields. You can see that qualification is defined as an array
of objects with two properties- name and year.
qualification: [ { name: '', year: '' } ]
- In the validation
Yup.array()
function is used to provide validation for the fields in qualification array. - The submission function (onSubmit) logs the form values.
- In the <Form> section some of the Bootstrap classes like
form-group
,form-label
,form-control
,is-invalid
,invalid-feedback
are used. - If there is an error then apart from form-control, is-invalid class is also added.
- For adding qualification, a button "Add Qualification" is provided. On clicking it a new object with two
fields- name, year is pushed into an array.
<button type="button" className="btn btn-secondary mb-2" onClick={() => push({ name: '', year: '' })}> Add Qualification </button>
- To give unique name to each group of fields in the array index is used
name={`qualification.${index}.year`}
getIn
, which is a utility function included in Formik is used to get errors for the specific index.- With each array index another button to remove that index is also added.
<button type="button" className="btn btn-danger" onClick={() => remove(index)}> X </button>
With field validation error messages
That's all for the topic React Dynamic Form Using Formik's FieldArray Component. If something is missing or you have something to share about the topic please write a comment.
You may also like
- React Form Using Formik's useFormik() Hook
- React Conditional Rendering With Examples
- React useRef Hook With Examples
- React App Flow - create-react-app Structure
- Java Program to Reverse a String In-place
- Encapsulation Vs Abstraction in Java - OOPS Concepts
- Spring Bean Scopes
- Spring Boot MVC Form Validation Example