Hone logo
Hone
Problems

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:

  1. Accept a list of options: Each option should have a unique value and a human-readable label.
  2. Manage selection state: The component should keep track of which radio button is currently selected.
  3. Handle user interaction: Clicking on a radio button should update the selected option.
  4. Be controllable: The component should accept an optional selectedValue prop and an onChange callback prop to integrate with parent component state management.
  5. 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> and htmlFor.
  • The name attribute 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 selectedValue is 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 onChange callback should be invoked with the value of the newly selected option whenever a selection changes.

Edge Cases:

  • What happens if the options array is empty?
  • What happens if the selectedValue prop is not present in the options?

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 options prop will be an array of objects, where each object has a label: string and a value: string.
  • The selectedValue prop, if provided, will be a string or undefined.
  • The onChange prop will be a function that accepts a string (the value of the selected option) and returns void.
  • The name prop will be a string and 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 id for each radio button and associate it with its label using htmlFor for accessibility.
  • The name attribute is crucial for radio button groups. Ensure all radio buttons within your component share the same name.
  • Think about how to handle the initial state and updates when selectedValue changes 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.
Loading editor...
typescript