Managing Complex Dependencies in Ant Design Forms
Learn how to use Ant Design's dependencies and shouldUpdate props to build cross-field validation and conditional inputs without state spaghetti.
01 Jun 2026, 10:42 UTC

The Problem: State Synchronization in Multi-Field Forms
You are building a signup form where the "Confirm Password" field must match the "Password" field, and a "Company Name" input should only appear if the user selects "Business" as their account type. The common instinct is to create a useState hook for every field and write a custom validation function that runs on submit. This quickly leads to "state spaghetti," where updating one field requires manually triggering the validation of another.
Ant Design (v4/v5) solves this by using a FormInstance. Instead of syncing React state, the form manages its own internal store. The key to avoiding manual state management is knowing when to use rules, dependencies, and shouldUpdate.
Field-Level Validation and Noise Control
Basic validation is handled via the rules prop on Form.Item. To prevent a poor user experience where errors appear after the first keystroke, use validateTrigger="onBlur". This ensures the user finishes typing before the form flags an error.
<Form.Item
name="email"
label="Email"
rules={[
{ required: true, message: 'Email is required' },
{ type: 'email', message: 'Please enter a valid email' },
]}
validateTrigger="onBlur"
>
<Input />
</Form.Item>
Cross-Field Validation with Dependencies
A common bug in password confirmation fields is that the "Confirm Password" field stays valid even if the user goes back and changes the original "Password" field. To fix this, use the dependencies prop. This tells the form to re-run the validation for this item whenever the listed fields change.
<Form.Item
name="confirm"
label="Confirm Password"
dependencies={['password']}
rules={[
{ required: true, message: 'Please confirm your password' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('Passwords do not match'));
},
}),
]}
>
<Input.Password />
</Form.Item>
In this configuration, the validator receives getFieldValue from the form instance. Because dependencies={['password']} is present, any change to the password field automatically triggers the confirmation validator, ensuring the UI remains consistent.
Conditional Rendering via shouldUpdate
When a field's existence depends on another field's value, avoid lifting that value into a component-level useState. Instead, use shouldUpdate. This prop allows a Form.Item to act as a wrapper that re-renders based on changes in the form store.
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.accountType !== currentValues.accountType}
>
{({ getFieldValue }) => (
getFieldValue('accountType') === 'business' ? (
<Form.Item
name="companyName"
label="Company Name"
rules={[{ required: true, message: 'Company name is required' }]}
>
<Input />
</Form.Item>
) : null
)}
</Form.Item>
The noStyle prop is critical here; it prevents the wrapper from adding unnecessary padding or margins to the layout. By default, when a field is unmounted (e.g., switching from "Business" to "Personal"), its value is removed from the onFinish payload, preventing stale data from being submitted.
Imperative Updates: setFieldsValue vs. initialValues
A frequent point of confusion is the difference between initialValues and form.setFieldsValue(). The initialValues prop is only applied when the component first mounts. If you are fetching user data from an API, initialValues will not update the form when the promise resolves.
To update a form after it has mounted, use the form instance created by Form.useForm():
const [form] = Form.useForm();
useEffect(() => {
api.getUserProfile().then(data => {
form.setFieldsValue({
email: data.email,
accountType: data.accountType,
});
});
}, [form]);
Trade-offs and Limitations
Ant Design's form system is highly opinionated. It uses an internal store that can be heavy in terms of bundle size compared to headless libraries like React Hook Form. Additionally, mixing controlled components (using value and onChange props) with Form.Item often leads to synchronization bugs. The general rule is: if a field is inside a Form.Item, let the form own the state entirely.
Verification Steps
To verify this implementation in your project:
- Check your
package.jsonto ensure you are onantdv4 or v5, as the v3 API is incompatible. - Test the password confirmation: fill both fields, then change the first password field and verify the second field immediately shows an error.
- Toggle the account type selector and confirm the company field appears/disappears without a full page reload.
- Submit the form and log the
onFinishvalues to ensure hidden fields are not included in the final payload.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.