Formik Architecture and Field Arrays0%

Formik Architecture and Field Arrays

Beginner12 min read•Updated: Jul 11, 2026
Study MaterialsCheatsheet

Formik Architecture and Field Arrays

Handling complex enterprise forms—with dynamic nested objects, repetitive rows, conditional validations, and asynchronous submission states—requires a robust form orchestration library. Formik manages form state, validation, error messages, and touched states. Its FieldArray component is the standard for managing dynamic repeatable lists (such as invoice line items, invite lists, or dynamic configuration rules).


1. Formik Core Architecture

Formik separates form state into four essential lifecycle models:

  • values: The current JavaScript object representing all input values.
  • errors: An object mirroring the keys of values containing string validation error messages.
  • touched: A boolean map tracking which inputs the user has physically interacted with and blurred, preventing premature error display.
  • isSubmitting: A boolean flag tracking asynchronous submission progress.

2. Dynamic Line Items with <FieldArray>

In applications like invoicing or procurement systems, users need to dynamically add, remove, and reorder line items. Formik's FieldArray provides helper methods (push, remove, swap, insert) while keeping validation errors in sync.

TSX
import React from "react";
import { Formik, Form, Field, FieldArray, ErrorMessage } from "formik";
import * as Yup from "yup";
 
interface LineItem {
description: string;
quantity: number;
unitPrice: number;
}
 
interface InvoiceFormValues {
clientName: string;
invoiceDate: string;
items: LineItem[];
}
 
const InvoiceSchema = Yup.object().shape({
clientName: Yup.string().required("Client name is required"),
invoiceDate: Yup.date().required("Invoice date is required"),
items: Yup.array()
.of(
Yup.object().shape({
description: Yup.string().required("Description required"),
quantity: Yup.number().min(1, "Min qty is 1").required("Required"),
unitPrice: Yup.number().min(0.01, "Min price is $0.01").required("Required"),
})
)
.min(1, "You must specify at least one line item"),
});
 
const initialValues: InvoiceFormValues = {
clientName: "",
invoiceDate: new Date().toISOString().split("T")[0],
items: [{ description: "Cloud Infrastructure Setup", quantity: 1, unitPrice: 1200 }],
};
 
export function InvoiceBuilder() {
return (
<div className="max-w-3xl p-8 bg-slate-900 text-white rounded-xl">
<h2 className="text-xl font-bold mb-6">Create Enterprise Invoice</h2>
 
<Formik
initialValues={initialValues}
validationSchema={InvoiceSchema}
onSubmit={async (values, { setSubmitting }) => {
console.log("Submitting Invoice:", values);
await new Promise((r) => setTimeout(r, 1000));
setSubmitting(false);
}}
>
{({ values, isSubmitting }) => (
<Form className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs text-slate-400 mb-1">Client Name</label>
<Field
name="clientName"
className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-sm text-white"
/>
<ErrorMessage name="clientName" component="div" className="text-rose-400 text-xs mt-1" />
</div>
 
<div>
<label className="block text-xs text-slate-400 mb-1">Date</label>
<Field
name="invoiceDate"
type="date"
className="w-full px-3 py-2 bg-slate-800 border border-slate-700 rounded text-sm text-white"
/>
<ErrorMessage name="invoiceDate" component="div" className="text-rose-400 text-xs mt-1" />
</div>
</div>
 
{/* Dynamic Repeatable Field Array */}
<div className="border-t border-slate-800 pt-4">
<h3 className="text-sm font-semibold text-cyan-400 mb-3">Invoice Line Items</h3>
 
<FieldArray name="items">
{({ push, remove }) => (
<div className="space-y-3">
{values.items.map((item, index) => (
<div key={index} className="flex gap-3 items-start bg-slate-950 p-3 rounded-lg border border-slate-800">
<div className="flex-1">
<Field
name={`items.${index}.description`}
placeholder="Item description"
className="w-full px-3 py-1.5 bg-slate-800 border border-slate-700 rounded text-xs text-white"
/>
<ErrorMessage
name={`items.${index}.description`}
component="div"
className="text-rose-400 text-xs mt-0.5"
/>
</div>
 
<div className="w-24">
<Field
name={`items.${index}.quantity`}
type="number"
placeholder="Qty"
className="w-full px-3 py-1.5 bg-slate-800 border border-slate-700 rounded text-xs text-white"
/>
<ErrorMessage
name={`items.${index}.quantity`}
component="div"
className="text-rose-400 text-xs mt-0.5"
/>
</div>
 
<div className="w-28">
<Field
name={`items.${index}.unitPrice`}
type="number"
placeholder="Price"
className="w-full px-3 py-1.5 bg-slate-800 border border-slate-700 rounded text-xs text-white"
/>
<ErrorMessage
name={`items.${index}.unitPrice`}
component="div"
className="text-rose-400 text-xs mt-0.5"
/>
</div>
 
{values.items.length > 1 && (
<button
type="button"
onClick={() => remove(index)}
className="px-2 py-1.5 bg-rose-900/40 text-rose-300 hover:bg-rose-900 rounded text-xs"
>
Remove
</button>
)}
</div>
))}
 
<button
type="button"
onClick={() => push({ description: "", quantity: 1, unitPrice: 0 })}
className="px-3 py-1.5 bg-slate-800 hover:bg-slate-700 text-cyan-400 text-xs font-semibold rounded border border-slate-700"
>
+ Add Item Row
</button>
</div>
)}
</FieldArray>
</div>
 
<button
type="submit"
disabled={isSubmitting}
className="w-full py-2.5 bg-cyan-600 hover:bg-cyan-500 font-semibold rounded text-sm disabled:opacity-50"
>
{isSubmitting ? "Generating Invoice..." : "Submit Invoice"}
</button>
</Form>
)}
</Formik>
</div>
);
}

3. Touched State and Validation Timing

Displaying errors when the form first mounts overwhelms users with red text before they have typed anything. Formik tracks field touched state via handleBlur:

TSX
// Only show error message if the user has focused and blurred away from the field
{touched.clientName && errors.clientName && (
<span className="text-xs text-rose-400">{errors.clientName}</span>
)}

Interactive Quiz

Practice Quiz

Question 1
What is the primary purpose of Formik's FieldArray component?
Question 2
Why does Formik maintain a touched state alongside errors and values?
Question 3
In Yup validation schemas, how do you validate an array of objects inside FieldArray?
Question 4
What does the isSubmitting boolean prop in Formik indicate?
Question 5
How do nested field names like name="items.0.description" function in Formik?
Next Lesson

Multi-Step Wizards and Dynamic Schema Validation with Yup

Continue learning with hands-on practice, examples, and exercises in the upcoming topic.

Practice Quiz

Test your understanding of this lesson with 5 questions. Each question has one correct answer.

PrevNext