Create a RadioGroup Component Dynamically#

Last Updated: October 18, 2023

My project source code is available here

Overview#

In React, we can create a radio group component dynamically using the React.Children.map() function. This will allow us to create a radio group component that can be used to render a list of radio buttons.

Rendering the RadioGroup component dynamically allows us to create a reusable component that can be used to render a list of radio buttons. Below is an example of rendering radio buttons manually, which is not reusable. Review the App.jsx file in the Files section below for a complete example.

Note

In this original implementation of App component, we are repeating the props checked and onChange in every RadioOption component. This is not following DRY (Don’t Repeat Yourself) principles. We can improve this using the React.Children.map() function and the React.cloneElement() function using the component composition paradigm to create the RadioGroup component.

function App() {
  const [selected, setSelected] = useState("");
  const handleChange = (e) => e.target.value === selected
  return (
    <div className="App">
      <h2>How did you hear about Little Lemon?</h2>
      <RadioGroup onChange={setSelected} selected={selected}>
        <RadioOption value="social_media" checked={false} onChange={handleChange}>Social Media</RadioOption>
        <RadioOption value="friends" checked={false} onChange={handleChange}>Friends</RadioOption>
        <RadioOption value="advertising" checked={false} onChange={handleChange}>Advertising</RadioOption>
        <RadioOption value="other" checked={false} onChange={handleChange}>Other</RadioOption>
      </RadioGroup>
      <button disabled={!selected}>Submit</button>
    </div>
  );
}

Files#

App.jsx#

Note

In App.jsx we are only passing the value to each RadioOption component. The RadioGroup component is using the React.Children.map() function and the React.cloneElement() function to clone the children and pass the correct props to each RadioOption component.

App.jsx
import "./App.css";
import { RadioGroup, RadioOption } from "./Radio";
import { useState } from "react";

/**
 * App displays a radio group built using component composition dynamically.
 * */
function App() {
  const [selected, setSelected] = useState("");
  // console.log("selected:", selected);
  return (
    <div className="App">
      <h2>How did you hear about Little Lemon?</h2>
      <RadioGroup onChange={setSelected} selected={selected}>
        <RadioOption value="social_media">Social Media</RadioOption>
        <RadioOption value="friends">Friends</RadioOption>
        <RadioOption value="advertising">Advertising</RadioOption>
        <RadioOption value="other">Other</RadioOption>
      </RadioGroup>
      <button disabled={!selected}>Submit</button>
    </div>
  );
}

export default App;

Radio/index.jsx#

Note

In Radio/index.jsx the RadioGroup component is using the React.Children.map() function and the React.cloneElement() function to clone the children and pass the correct props to each RadioOption component.

The RadioOption component is using the onChange prop passed to the RadioGroup component and then passed to the RadioOption component to handle the onChange event. The checked prop is also passed to the RadioOption component to handle the checked state of the radio button.

Radio/index.jsx
import * as React from "react";
import "./styles.css";

export const RadioGroup = ({ onChange, selected, children }) => {
  // Use React.Children.map and React.cloneElement to clone the children
  // and pass the correct props to each RadioOption
  const RadioOptions = React.Children.map(children, (child) => {
    return React.cloneElement(child, {
      ...child.props,
      onChange: onChange,
      checked: child.props.value === selected,
    });
  });
  return <div className="RadioGroup">{RadioOptions}</div>;
};

export const RadioOption = ({ value, checked, onChange, children }) => {
  // Hook up the onChange handler to call the onChange prop passed to RadioGroup
  // Also, make sure to pass the correct checked prop to the input element
  return (
    <div className="RadioOption">
      <input
        id={value}
        type="radio"
        name={value}
        value={value}
        checked={checked}
        onChange={(e) => {
          onChange(e.target.value);
        }}
      />
      <label htmlFor={value}>{children}</label>
    </div>
  );
};