Nick
Nick

Reputation: 2551

How do you toggle the checked value of a radio button in react using hooks?

I have a group of radio buttons being built up from some external data, as per the examples below. They render and work as I'd expect with the exception of the aria-checked attribute. Clicking a radio button toggles the checked value, but it remains true when another has been selected.

If I click each radio button in sequence, I end up with a list of radio buttons that display aria-checked="true" which obviously isn't the best experience.

I'm not sure how to go about toggling the radio buttons checked value back to false when another has been checked.

Any help would be great!

Input:

    const [isChecked, setIsChecked] = useState(false);

    <Input
        type={type}
        id={id}
        value={id}
        name={name}
        aria-label={label}
        aria-checked={isChecked}
        onChange={() => setIsChecked(!isChecked)}
    />
    <Label htmlFor={id}>{label}</Label>

Usage:

    <fieldset>
        {someData.map(item => {
            return (
                <RadioButton
                    type="radio"
                    key={item.id}
                    id={item.id}
                    name={item.name}
                    label={item.label}
                />
            );
        })}
    </fieldset>

Upvotes: 3

Views: 11918

Answers (2)

HMR
HMR

Reputation: 39250

You could try something like this.

const Radio = React.memo(function Radio({
  item,
  checked,
  onChange
}) {
  console.log("rendering", item.label);
  return (
    <label>
      {item.label}
      <input
        type="radio"
        value={item.id}
        checked={checked}
        onChange={onChange}
      />
    </label>
  );
});
const RadioList = ({ items, value, onChange }) => (
  <div>
    {items.map(item => (
      <Radio
        item={item}
        key={item.id}
        checked={item.id === value}
        onChange={onChange}
      />
    ))}
  </div>
);

const App = ({ items }) => {
  const [value, setValue] = React.useState(items[0].id);
  const [v, setV] = React.useState(items[1].id);
  const onChange = React.useCallback(
    e => setValue(e.target.value),
    []
  );
  const onOther = React.useCallback(
    e => setV(e.target.value),
    []
  );
  return (
    <div>
      <RadioList
        items={items}
        value={value}
        onChange={onChange}
      />
      <RadioList
        items={items}
        value={v}
        onChange={onOther}
      />
    </div>
  );
};
//render app
ReactDOM.render(
  <App
    items={[
      { id: "1", label: "one" },
      { id: "2", label: "two" },
      { id: "3", label: "three" },
      { id: "4", label: "four" }
    ]}
  />,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 4

Vishal Sharma
Vishal Sharma

Reputation: 336

There are two things you should change here.

  1. When you are rendering a list of radio buttons under the same group, then the name attribute should be same for all the radio buttons. So instead of reading the name attribute from the someData, pass the same name attribute to each button. Or keep the same name in someData for each object.

  2. After that, I don't think the state should be on input component because once you checked the radio, you are never changing its value even after clicking on some radio button. You can try it keeping the same name attribute on each Radio button.

So the solution can be to actually pass the checked attribute to the Input component depending upon which radio is actually selected. Please check the following code:

const RadioButton = ({ type, id, name, label, checked, onChange }) => {
return (
    <>
        <input
            type={type}
            id={id}
            value={id}
            name={name}
            aria-label={label}
            aria-checked={checked}
            checked={checked}
            onChange={onChange}
        />
        <label htmlFor={id}>{label}</label>
    </>
)

}

const RadioGroup = () => {
const someData = [{
    id: 1,
    name: 'name',
    label: 'name 1'
}, {
    id: 2,
    name: 'name',
    label: 'name 2'
}, {
    id: 3,
    name: 'name',
    label: 'name 3'
}];

const [checkedValue, setIsChecked] = useState(1);

return (
    <fieldset>
        {someData.map(item => {
            return (
                <RadioButton
                    type="radio"
                    key={item.id}
                    id={item.id}
                    name="radioGroup"
                    label={item.label}
                    checked={checkedValue === item.id}
                    onChange={() => setIsChecked(item.id)}
                />
            );
        })}
    </fieldset>
)

}

Upvotes: 1

Related Questions