How to Implement a React Radio Button in ReactJS: The Complete Controlled Component Guide
The recommended approach is to create a controlled radio button by binding the checked property to React state and updating it via an onChange handler, ensuring the UI always reflects your application's source of truth.
Radio buttons are a staple of web forms, but implementing them correctly in React requires understanding how the library manages form state. In the facebook/react repository, radio inputs are handled as controlled components where React state serves as the single source of truth. This article explores the recommended patterns for implementing a react radio button, referencing the actual source code in ReactDOMInput.js and ChangeEventPlugin.js to explain why the controlled component pattern works best.
Controlled vs Uncontrolled React Radio Buttons
The Controlled Component Pattern (Recommended)
When building a react radio button, the controlled pattern stores the selected value in component state. The checked attribute (or the group's shared value) is driven by this state, guaranteeing that the UI always reflects the source of truth. This approach prevents mismatches that occur when the component re-renders.
The Uncontrolled Alternative
Uncontrolled radios rely on the DOM's internal state, which can lead to inconsistencies. While React supports uncontrolled components via defaultChecked, this pattern is rarely needed for radio buttons because it sacrifices the predictability of React's declarative model.
function UncontrolledRadio() {
return (
<label>
<input type="radio" name="color" value="red" defaultChecked />
Red
</label>
);
}
How Radio Button Groups Work in React
To create a mutually exclusive group, give every radio input the same name attribute. React treats them as a group where only the radio whose value matches the state receives checked={true}.
According to the source code in packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js, React normalizes the native change event for radios. The plugin fires onChange only on the selected radio, matching browser behavior while ensuring consistency across different environments.
Step-by-Step Implementation of a React Radio Button Group
Follow these steps to implement a controlled radio button group:
- Declare state to hold the selected value using
useState. - Render each input with
type="radio", identicalnameattributes, distinctvalueprops, andchecked={stateValue === value}. - Attach an
onChangehandler that updates state toevent.target.value.
Here is a complete implementation:
function ColorPicker() {
const [selected, setSelected] = React.useState('green');
const handleChange = e => setSelected(e.target.value);
return (
<fieldset>
<legend>Choose a color</legend>
<label>
<input
type="radio"
name="color"
value="red"
checked={selected === 'red'}
onChange={handleChange}
/>
Red
</label>
<label>
<input
type="radio"
name="color"
value="green"
checked={selected === 'green'}
onChange={handleChange}
/>
Green
</label>
<label>
<input
type="radio"
name="color"
value="blue"
checked={selected === 'blue'}
onChange={handleChange}
/>
Blue
</label>
<p>Selected: {selected}</p>
</fieldset>
);
}
For a single standalone radio button:
function ControlledSingle() {
const [isChecked, setChecked] = React.useState(false);
return (
<label>
<input
type="radio"
checked={isChecked}
onChange={e => setChecked(e.target.checked)}
/>
Accept terms
</label>
);
}
The Internals: How React Handles Radio Input Events
The implementation details in packages/react-dom/src/client/ReactDOMInput.js reveal how React manages radio groups internally. When the name prop changes, React temporarily disconnects radios from their groups to maintain the invariant that exactly one radio in a group can be checked.
The ChangeEventPlugin located at packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js ensures that onChange fires correctly. Unlike standard DOM events where change events behave differently across browsers, React's synthetic event system normalizes this behavior, firing onChange only on the radio being selected.
Test cases in packages/react-dom/src/__tests__/DOMPropertyOperations-test.js verify that radio buttons properly fire events and maintain grouping invariants, ensuring reliable behavior across React versions.
Summary
- Use controlled components: Bind the
checkedproperty to React state and update it viaonChangehandlers for predictable UI synchronization. - Group with name attributes: Assign identical
nameattributes to radios in the same group to enforce mutual exclusivity. - Leverage React's event system: React normalizes radio change events through
ChangeEventPlugin.js, ensuring consistent cross-browser behavior. - Avoid manual DOM manipulation: Let
ReactDOMInput.jshandle group membership changes and event wiring internally.
Frequently Asked Questions
Should I use controlled or uncontrolled radio buttons in React?
Controlled radio buttons are strongly recommended. Controlled components store the selected value in React state, ensuring the UI remains synchronized with your application's data. Uncontrolled components using defaultChecked can lead to stale UI states when components re-render.
Why must radio buttons in a group share the same name attribute?
The name attribute identifies which inputs belong to a mutually exclusive group. According to the React source code in ReactDOMInput.js, the name prop determines group membership, and React uses this to ensure only one radio button in the group can be checked at a time.
How does React normalize radio button change events?
React uses the ChangeEventPlugin (packages/react-dom-bindings/src/events/plugins/ChangeEventPlugin.js) to normalize native change events. This ensures the onChange handler fires consistently across browsers and only triggers on the radio button being selected, not the one losing selection.
Can I implement a radio button group without using state?
While technically possible using refs (uncontrolled components), it is not recommended. Without React state, you lose the declarative benefits of the framework, making it difficult to programmatically set values, validate forms, or persist selections across re-renders.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md