React Radio Button Group Component
This challenge focuses on building a fundamental UI component: a radio button group. Radio buttons are essential for allowing users to select a single option from a predefined set, making them crucial for forms and user preference settings in React applications. You will implement a reusable component that handles selection state and renders accessible radio buttons.
Problem Description
Your task is to create a reusable React component in TypeScript that renders a group of radio buttons. This component should:
- Accept a list of options: Each option should have a unique
valueand a human-readablelabel. - Manage selection state: The component should keep track of which radio button is currently selected.
- Handle user interaction: Clicking on a radio button should update the selected option.
- Be controllable: The component should accept an optional
selectedValueprop and anonChangecallback prop to integrate with parent component state management. - Render accessible radio buttons: Ensure proper ARIA attributes are used for accessibility.
Key Requirements:
- The component should be a functional component using React hooks.
- TypeScript should be used for type safety.
- Each radio button should be associated with its label using
<label>andhtmlFor. - The
nameattribute for all radio buttons within a group should be the same, ensuring browser-level grouping. - The component should visually indicate the currently selected radio button.
Expected Behavior:
- When the component mounts, if
selectedValueis provided, the corresponding radio button should be pre-selected. - Clicking an unselected radio button should deselect the previously selected one and select the new one.
- The
onChangecallback should be invoked with thevalueof the newly selected option whenever a selection changes.
Edge Cases:
- What happens if the
optionsarray is empty? - What happens if the
selectedValueprop is not present in theoptions?
Examples
Example 1:
// Parent Component
import React, { useState } from 'react';
import RadioButtonGroup from './RadioButtonGroup'; // Assuming your component is named RadioButtonGroup
const options = [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
{ label: 'Option C', value: 'c' },
];
function App() {
const [selected, setSelected] = useState('b'); // Initial selected value
const handleSelectionChange = (value: string) => {
setSelected(value);
console.log(`Selected: ${value}`);
};
return (
<div>
<h2>Choose an option:</h2>
<RadioButtonGroup
options={options}
selectedValue={selected}
onChange={handleSelectionChange}
name="myRadioGroup" // Important for grouping
/>
</div>
);
}
export default App;
// RadioButtonGroup Component (your implementation)
// ... will render radio buttons for A, B, C
// 'Option B' will be checked initially.
// Clicking 'Option A' will update 'selected' to 'a' and call onChange('a').
Example 2:
// Parent Component
import React, { useState } from 'react';
import RadioButtonGroup from './RadioButtonGroup';
const colorOptions = [
{ label: 'Red', value: 'red' },
{ label: 'Green', value: 'green' },
{ label: 'Blue', value: 'blue' },
];
function ColorSelector() {
const [chosenColor, setChosenColor] = useState<string | undefined>(undefined); // No initial selection
const handleColorChange = (color: string) => {
setChosenColor(color);
};
return (
<div>
<h3>Select your favorite color:</h3>
<RadioButtonGroup
options={colorOptions}
selectedValue={chosenColor}
onChange={handleColorChange}
name="color"
/>
{chosenColor && <p>You selected: {chosenColor}</p>}
</div>
);
}
export default ColorSelector;
// RadioButtonGroup Component (your implementation)
// ... will render radio buttons for Red, Green, Blue
// No radio button will be checked initially.
// Clicking 'Green' will select it and call onChange('green').
// The parent will then display "You selected: green".
Example 3 (Edge Case: Empty Options):
// Parent Component
import React from 'react';
import RadioButtonGroup from './RadioButtonGroup';
const emptyOptions: { label: string; value: string }[] = [];
function EmptySelector() {
return (
<div>
<h4>Empty Group:</h4>
<RadioButtonGroup
options={emptyOptions}
selectedValue=""
onChange={() => {}}
name="empty"
/>
{/* This should render nothing or a visually empty container */}
</div>
);
}
export default EmptySelector;
// RadioButtonGroup Component (your implementation)
// ... should gracefully handle the empty options array and render no radio buttons.
Constraints
- The
optionsprop will be an array of objects, where each object has alabel: stringand avalue: string. - The
selectedValueprop, if provided, will be astringorundefined. - The
onChangeprop will be a function that accepts astring(the value of the selected option) and returnsvoid. - The
nameprop will be astringand must be unique for each radio button group on the page to ensure proper browser behavior. - The component should be performant for up to 50 options.
Notes
- Consider how to provide a unique
idfor each radio button and associate it with itslabelusinghtmlForfor accessibility. - The
nameattribute is crucial for radio button groups. Ensure all radio buttons within your component share the samename. - Think about how to handle the initial state and updates when
selectedValuechanges from the parent. - You are encouraged to use CSS for basic styling, but the primary focus is on the component's logic and structure.
- Accessibility is a key aspect of this challenge. Ensure your radio buttons are usable with keyboard navigation and screen readers.