NOTE: This article is written by AI but it is based on real research and bug-fixing.
When building custom form components in React with Ant Design, you might encounter a frustrating issue where your Form
's onChange
event doesn't fire despite your custom component correctly updating its value. This article explores why this happens and provides a clean solution.
The Problem
You've created a custom component that integrates with Ant Design's Form.Item
, everything seems to work correctly - your component receives value
and onChange
props, calls onChange
when the value changes, and the form field updates. But the parent form's onChange
event never fires.
Minimal Reproducible Example
Here's a simplified example that demonstrates the issue:
{ Form, Radio, Button } from 'antd';
div
import { useState } from 'react';
interface CustomRadioGroupProps {
value?: string;
onChange?: (value: string) => void;
}
// Problematic implementation
const ProblematicCustomRadioGroup = ({ value, onChange }: CustomRadioGroupProps) => {
const options = ['option1', 'option2', 'option3'];
return (
<div>
{options.map(option => (
< key={option}
style={{ padding: '10px',
border: '1px solid #ccc',
margin: '5px',
cursor: 'pointer',
backgroundColor: value === option ? '#1890ff' : 'white' }}
onClick={() => onChange?.(option)} // This won't trigger form onChange!
>
<Radio checked={value === option} />
{option}
</div>
))}
</div>
);
};
const App = () => {
const [form] = Form.useForm();
const handleFormChange = () => {
console.log('Form changed!'); // This never fires with the problematic version
};
return (
<Form form={form} onChange={handleFormChange}>
<Form.Item name="selection" label="Select an option">
<ProblematicCustomRadioGroup />
</Form.Item>
<Button htmlType="submit">Submit</Button>
</Form>
);
};
In this example, clicking on the divs updates the component's value and calls onChange
, but the form's onChange
handler never executes.
Why This Happens
Ant Design's Form system tracks changes by listening for native DOM events from form controls. When you programmatically call onChange
from a click event on a div, you're updating the form field value, but you're not generating the native input events that Ant Design expects.
The key insight is:
- Programmatic
onChange
calls → Updates field value but no form event - Native input events → Updates field value AND triggers form events
The Solution
The fix is to trigger the actual input element's click event instead of calling onChange
directly:
{ Form, Radio, Button } from 'antd';
div
import { useState, useRef } from 'react';
import { RadioRef } from 'antd/es/radio';
interface CustomRadioGroupProps {
value?: string;
onChange?: (value: string) => void;
}
// Fixed implementation
const FixedCustomRadioGroup = ({ value, onChange }: CustomRadioGroupProps) => {
const options = ['option1', 'option2', 'option3'];
const radioRefs = useRef<Record<string, RadioRef | null>>({});
return (
<div>
{options.map(option => (
< key={option}
style={{ padding: '10px',
border: '1px solid #ccc',
margin: '5px',
cursor: 'pointer',
backgroundColor: value === option ? '#1890ff' : 'white' }}
Radio
onClick={() => {
// Trigger the actual radio input click - this generates native events!
radioRefs.current[option]?.input?.click();
}}
>
< ref={(ref) => { radioRefs.current[option] = ref; }}
checked={value === option} onChange={() => onChange?.(option)}
/>
{option}
</div>
))}
</div>
);
};
const App = () => {
const [form] = Form.useForm();
const handleFormChange = () => {
console.log('Form changed!'); // This now fires correctly!
};
return (
<Form form={form} onChange={handleFormChange}>
<Form.Item name="selection" label="Select an option">
<FixedCustomRadioGroup />
</Form.Item>
<Button htmlType="submit">Submit</Button>
</Form>
);
};
Key Changes Explained
- Added refs to radio inputs:
radioRefs.current[option] = ref
- Trigger native click events:
radioRefs.current[option]?.input?.click()
- Keep onChange on Radio: The Radio component's
onChange
handles the actual value update
Why This Works
When you call radioRef.current.input.click()
:
- Native click event is triggered on the actual radio input
- Radio's onChange fires with a proper
ChangeEvent
object - Ant Design detects this as a legitimate form field change
- Form's onChange event gets triggered automatically
Additional Benefits
This approach also provides:
- Better accessibility - screen readers and keyboard navigation work properly
- Consistent behavior - matches native form control behavior
- Future compatibility - works with all Ant Design form features
Conclusion
When building custom form components for Ant Design, remember that the Form system expects native DOM events to trigger its change detection. Instead of manually calling onChange
from custom event handlers, trigger the underlying input element's events to ensure proper integration with Ant Design's form management system.
This pattern ensures your custom components work seamlessly with form validation, submission handling, and all other Ant Design Form features.