Dynamic Form with react-hook-form useFieldArray

October 31, 2022

3 min read

Dynamic Form with react-hook-form useFieldArray
Watch on YouTube

Let's make a dynamic form with react-hook-form. Here we have a job application form where you can add a list of experiences.


We use yup to define the schema for our form that matches the JobApplicationForm type. To validate the experience list, we pass the yup object to the "array().of()" sequence. In the useJobApplicationForm, we defined default values for the form to show one experience sub-form to the user.

import { useForm } from "react-hook-form"
import * as yup from "yup"
import { yupResolver } from "@hookform/resolvers/yup"

export const bioMaxLength = 300
export const responsibilityMaxLength = 300

interface JobExperienceShape {
  position: string
  responsibility: string

export interface JobApplicationFormShape {
  name: string
  bio: string
  experience: JobExperienceShape[]

const schema: yup.SchemaOf<JobApplicationFormShape> = yup.object({
  name: yup.string().max(100).required(),
  bio: yup.string().max(bioMaxLength).required(),
  experience: yup
        position: yup.string().min(4).required(),
        responsibility: yup

export const emptyExperience: JobExperienceShape = {
  position: "",
  responsibility: "",

export const useJobApplicationForm = () => {
  return useForm<JobApplicationFormShape>({
    mode: "onSubmit",
    resolver: yupResolver(schema),
    defaultValues: {
      experience: [emptyExperience],

Here we set up our form component with fields for general info and a section to list the experience. To work with the list of sub-forms, we leverage the useFieldArray hook. We pass the control function and the name of the field array and receive functions for managing a dynamic form.

We display sub-form by iterating over the fields array and displaying experience number with a remove button on the left side and content on the right. To remove an experience, we call the remove function, and to handle inputs, we pass to the register function a template string that includes the name of the sub-form, index, and field name. The same goes for the error message.

export const ExperienceSection = ({
  form: {
    formState: { errors },
}: Props) => {
  const { fields, append, remove } = useFieldArray({
    name: "experience",

  return (
    <FormSection name="Experience">
      {fields.map((field, index) => (
        <VStack key={index} fullWidth gap={16}>
          <HStack fullWidth gap={24}>
            <VStack gap={8}>
              <ExperienceNumber size={manageElementSizeInPx}>
                <Text>{index + 1}</Text>
                onClick={() => remove(index)}
                icon={<TrashIcon />}
            <VStack fullWidth gap={16}>
                placeholder="Senior Front End Engineer"
                placeholder="I was responsible for ..."
          <Line />
      <VStack alignItems="start">
          onClick={() => append(emptyExperience)}
          Add experience

Since we have the onSubmit mode for validating the form, we don't see the errors while filling up the form, but they show up when we try to submit the form.