Race condition during asynchronous validation in Ant Design Form
0 reputation · 01 Feb 2022, 23:44 UTC
0 reputation · 01 Feb 2022, 23:44 UTC
The Ant Design Form component manages field validation via an internal store. When custom validation rules return a Promise, the component initiates an asynchronous request to determine the field's validity. Under rapid user input with validateTrigger="onChange", multiple validation requests can be dispatched concurrently.
Because these asynchronous requests may resolve in a different order than they were initiated, there is a risk that an earlier request resolving last could overwrite the validation state of a more recent input. This behavior can lead to a mismatch between the current field value and the displayed validation message.
Does the Form store implement an internal mechanism to ignore stale Promise resolutions? If not, what is the recommended pattern to ensure the final validation state aligns with the most recent input?
33275 reputation · 02 Feb 2022, 07:05 UTC
The Ant Design Form store does not automatically discard stale Promise resolutions from async validators. If two validation requests finish out of order, the later‑resolving promise can overwrite the validation state of an earlier, more recent input, causing a mismatch between the field value and the displayed message.
When validateTrigger="onChange" is used, each keystroke triggers a new validation promise. The form tracks each promise independently but does not associate them with a monotonic request ID or cancel previous ones. Consequently, the state update from whichever promise resolves last wins, regardless of whether it corresponds to the current field value.
Form.validateFields returns a promise that resolves after all specified field validators complete; each async validator is invoked immediately and its promise is tracked independently.onChange events do not launch overlapping promises (e.g., using lodash.debounce on the field’s onChange handler).validateTrigger to "onBlur" to avoid concurrent validation altogether.onChange, wrap each async validator in a utility that compares the current field value with the value captured when the promise started, and only update state if they still match.Form.useForm, you can expose a custom validateFields wrapper that increments a request ID and ignores callbacks with outdated IDs.To tailor the recommendation further, could you confirm whether you are using the Form.useForm pattern or the legacy ref‑based form instance?
Use comments to ask for clarification. Post a solution as an answer.
33,275 reputation · 02 Feb 2022, 11:23 UTC
Ant Design v5 does attach a monotonically increasing validation timestamp to each field when validation is triggered through the Form API (Form.validateFields or Form.useForm). This timestamp lets the store ignore stale resolutions for those internal calls. However, custom async validators supplied via rules bypass this mechanism—they receive only the field value and a callback, with no access to the internal timestamp. Consequently, if you rely solely on the rule’s promise, the store will still overwrite the validation state with whichever promise resolves last, regardless of input order.
To benefit from the built‑in safeguard, you can wrap your custom validator in a function that captures the current validation timestamp from the form instance and compares it before updating the state. Example:
const timestampRef = useRef(0);
const { validateFields } = form;
function asyncValidator(value) {
const current = ++timestampRef.current;
return new Promise((resolve) => {
// simulate async work
setTimeout(() => {
if (current === timestampRef.current) {
// only update if this is the latest request
resolve(value.length > 3 ? Promise.resolve() : Promise.reject('Too short'));
}
}, 500);
});
}
// usage
This pattern ensures that only the validator associated with the most recent input can change the field’s validation status, eliminating the race condition without external debouncing.