Dynamic Nested Forms in Ant Design: Using Form.List for Efficient Data Entry
Dynamic nested forms are hard—Ant Design’s Form.List simplifies array handling, validation, and state sync. Learn how to build, validate, and keep performance high.
30 Nov 2025, 02:21 UTC

Problem: Nested Data Entry Is Hard to Manage
When a form contains arrays of objects—think invoices with line items or a user profile with multiple addresses—keeping the UI and the underlying data in sync is a pain. A vanilla React approach requires manual state updates, index handling, and often leads to excessive re‑renders.
Thesis: Ant Design’s Form.List Solves It
Form.List abstracts the array logic, giving you a declarative API to add, remove, and validate items while the form instance maintains the internal store. Coupled with useForm and onValuesChange, you get real‑time feedback and clean code.
1️⃣ Declarative Field Registration
Form.registerField (internally) tracks values by a unique key. On every change, the form instance updates only the affected field, then triggers re‑validation. In a nested list, the key is a path like emails[0].address.
2️⃣ Working Example: Dynamic Email List
import React from 'react';
import { Form, Input, Button, Space } from 'antd';
const DynamicEmailForm = () => {
const [form] = Form.useForm();
const onFinish = values => {
console.log('Submitted values:', values);
};
const validateEmail = async (_, value) => {
if (!value) return Promise.reject('Email required');
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/;
return emailRegex.test(value)
? Promise.resolve()
: Promise.reject('Invalid email address');
};
return (
console.log('Values changed', all)}
>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
remove(name)}>
Remove
))}
add()} block>
Add Email
)}
Submit
form.resetFields()}>
Reset
);
};
export default DynamicEmailForm;
Running this component in a React app will render a list where users can add or remove email entries. The form payload on submit will look like:
{ "emails": [ { "address": "[contact removed]", "type": "work" }, … ] }
3️⃣ Cross‑Field Validation in a List
Suppose each email must be unique across the list. You can enforce this with dependencies and a custom validator that checks the entire array:
<Form.Item
name={[name, 'address']}
dependencies={['emails']}
rules={[
{ validator: (_, value) => {
const all = form.getFieldValue('emails') || [];
const duplicate = all.filter(e => e.address === value).length > 1;
return duplicate ? Promise.reject('Duplicate email') : Promise.resolve();
}
}
]}
/>
4️⃣ Performance Trade‑offs
- Every keystroke in a nested field triggers a re‑render of the entire
Form.Listcomponent tree unlessReact.memoorshouldComponentUpdateis used. - Deeply nested forms can lead to noticeable lag on low‑end devices.
- Migration from Ant Design 4.x to 5.x requires updating the form instance API (
form.setFieldsValuevs.form.setFields) and validation syntax (rules changed tovalidatorreturning a Promise).
5️⃣ Practical Checklist Before Deploying
- Render the form in a minimal React app and verify that
form.getFieldValuereturns the expected nested structure. - Use React DevTools Profiler to confirm that adding a new email only updates the relevant list items.
- Test async validation by mocking a server call inside
validateEmailand ensuring the UI shows a loading spinner. - Run
npm run lintto catch any unused imports or missing keys. - If upgrading to Ant Design 5.x, replace deprecated APIs and run the migration guide.
Conclusion
Ant Design’s Form.List turns a potentially chaotic nested form into a declarative, maintainable component. By leveraging useForm, onValuesChange, and careful validation rules, you can build complex data entry interfaces that are both user‑friendly and performant. Keep performance in mind, profile re‑renders, and follow the checklist to ensure a smooth integration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.