Building Scalable Forms with Ant Design’s Dynamic Field Generation
Learn how to use Ant Design’s Form and useForm hook to create maintainable, dynamic fields—complete with a working code example, validation tips, and performance considerations.
18 Dec 2025, 01:47 UTC

Problem: Forms that grow with user input
When a form needs to collect a variable number of similar entries—such as multiple phone numbers, email addresses, or address lines—hard‑coding each field quickly becomes brittle. Adding or removing fields requires touching the render logic, validation rules, and initial values, which makes the component hard to maintain as the form evolves.
Thesis: Use useForm and Form.List for declarative dynamic fields
Ant Design’s Form component, paired with the useForm hook, lets you define a field path (e.g., emails[0].value) and render a repeatable block with Form.List. The form state stays in sync automatically, and validation rules can be attached to the same path.
Setting up a dynamic email list
Below is a minimal, self‑contained example that works with Ant Design v4 or v5 (the API for useForm and Form.List is compatible across those versions).
import React from 'react';
import { Form, Input, Button, Space } from 'antd';
const EmailForm = () => {
const [form] = Form.useForm();
const onFinish = (values) => {
console.log('Submitted values:', values);
// Expected shape: { emails: [{ value: '[contact removed]' }, { value: '[contact removed]' }] }
};
return (
{(fields, { add, remove }) => (
<>
{fields.map((field, index) => (
remove(field.name)}
disabled={fields.length === 1>
>
Remove
))}
add()} block>
Add email
)
Submit
);
};
export default EmailForm;
How the pieces work
Form.useForm()returns a form instance that manages values and validation.- The
nameprop onForm.List("emails") tells Ant Design to treat the children as an array stored undervalues.emails. - Each field inside the list uses a nested name path:
{[field.name, 'value']}resolves toemails[index].value. - Validation rules are attached to the same path, so errors appear inline for each entry.
- The
addandremovecallbacks mutate the form’s internal state; the UI updates automatically.
Trade‑offs and limitations
While this pattern keeps the form declarative, there are practical considerations:
- Performance: Rendering dozens of fields can cause re‑render spikes. For large lists, consider virtualization (e.g.,
react-window) or pagination to limit the DOM nodes. - Version differences: Ant Design v4 used
form.getFieldInstancefor some advanced cases; v5 relies entirely onuseForm. If you upgrade, check the migration guide for renamed props likevaluePropName(still present) and ensure peer dependencies (@ant-design/icons) match. - Validation complexity: Cross‑field rules (e.g., ensuring at least one email is provided) require a custom validator function that receives the whole
valuesobject. Keep these functions pure and unit‑testable.
Practical verification steps
- Confirm the installed version: run
npm list antdin your project root. The output should show a version like4.24.0or5.12.0. - Add the
EmailFormcomponent to a route or page and start the dev server (npm startoryarn dev). - Click “Add email” a few times, fill in values, and submit. Open the browser console; you should see an object with an
emailsarray containing the entered strings. - Leave an email blank and submit; the inline error message “Please input an email” should appear for that field.
If the console shows the expected shape and validation works, the setup is correct. If you notice lag with >30 fields, profile the render time with React DevTools and consider virtualizing the list.
Actionable closing
Start small: implement a single dynamic field using the pattern above, verify submission and validation, then scale to more complex structures (nested arrays, conditional fields). Keep the form state in useForm, leverage Form.List for repeatable blocks, and monitor performance as the list grows. This approach gives you a maintainable, declarative form that scales with your UI needs without scattering imperative DOM manipulation across your codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.