February 26, 2023

React Dynamic Form Using Formik's FieldArray Component

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.

Formik FieldArray

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">&nbsp;</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-

  1. 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" />
    	
  2. 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: ''
      }
    ]
       
  3. In the validation Yup.array() function is used to provide validation for the fields in qualification array.
  4. The submission function (onSubmit) logs the form values.
  5. In the <Form> section some of the Bootstrap classes like form-group, form-label, form-control, is-invalid, invalid-feedback are used.
  6. If there is an error then apart from form-control, is-invalid class is also added.
  7. 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>
    
  8. To give unique name to each group of fields in the array index is used
    name={`qualification.${index}.year`}
    
  9. getIn, which is a utility function included in Formik is used to get errors for the specific index.
  10. 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

React dynamic form validation

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

No comments:

Post a Comment