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';
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 => (
<
div
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';
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 => (
<
div
key={option}
style={{

padding: '10px',
border: '1px solid #ccc',
margin: '5px',
cursor: 'pointer',
backgroundColor: value === option ? '#1890ff' : 'white'

}}
onClick={() => {
// Trigger the actual radio input click - this generates native events!
radioRefs.current[option]?.input?.click();
}}
>
<
Radio
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

  1. Added refs to radio inputsradioRefs.current[option] = ref
  2. Trigger native click eventsradioRefs.current[option]?.input?.click()
  3. Keep onChange on Radio: The Radio component's onChange handles the actual value update

Why This Works

When you call radioRef.current.input.click():

  1. Native click event is triggered on the actual radio input
  2. Radio's onChange fires with a proper ChangeEvent object
  3. Ant Design detects this as a legitimate form field change
  4. 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.