Solution 1 :

Similar issue is discussed on OnChange event handler for radio button (INPUT type=”radio”) doesn’t work as one value

A solution to this is to handle the changes and the states of the radio buttons on the parent element .

An example of how this is done :

const Parent = () => {
  const [buttons, setButtons] = useState([0, 0, 0]);
  const handleClick = (index) => {
    setButtons((buttons) => {
      alert(`prev radio ${buttons}`);
      // You add a condition of deselecting here if you want
      // if(buttons[index] === 1 ) return [0,0,0]
      const newButtons = [0, 0, 0];
      newButtons[index] = 1;
      alert(`new radio ${newButtons}`);
      return newButtons;
    });
  };

  return (
    <form>
      {buttons.map((_, index) => (
        <RadioButton
          handleClick={handleClick}
          key={index}
          index={index}
          checked={buttons[index]}
        />
      ))}
    </form>
  );
};

const RadioButton = ({ index, checked, handleClick }) => {

  return (
    <input
      value={"" + index}
      type="radio"
      onChange={() => handleClick(index)}
      checked={checked}
    />
  );
};

If you need some labels or other thing next to the radio button you can wrap them together ( replace RadioButton component with the component below )

const CustomInputWithOtherInfo = ({ index, checked, handleClick }) => {
  const otherInfo = [
    {
      label:"label1",
      title:"title1"     
    },
    {
      label:"label2",
      title:"title2"     
    },
    {
      label:"label3",
      title:"title3"     
    },
  ]
  return (
    <div>
      <h4>{otherInfo[index].title}</h4>
      <label>{otherInfo[index].label}</label>
      <input
        value={"" + index}
        type="radio"
        onChange={() => handleClick(index)}
        checked={checked}
      />
    </div>
  );
};

here you a can find a demo on sandbox

https://codesandbox.io/s/react-playground-forked-ik1lo0?file=/index.js:129-335

Solution 2 :

This is an over-kill. But since you asked, so here you go:

import React, { useEffect, useState } from "react";
import "./styles.css";

const arrRadios = [
  {
    id: "1",
    text: "Text1",
    checked: false
  },
  {
    id: "2",
    text: "Text2",
    checked: false
  },
  {
    id: "3",
    text: "Text3",
    checked: false
  }
];

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <RadioGroup arrRadios={arrRadios} groupId="myGroup" />
    </div>
  );
}

function RadioGroup(props) {
  const [arrRadios, setArrRadios] = useState(props.arrRadios);
  const handleChange = (e) => {
    console.log(e.target.value);
    arrRadios.forEach((a) => {
      if (a.id === e.target.value) {
        a.checked = true;
      } else {
        a.checked = false;
        dispatchEvent(new Event(`rb-deselect-${props.groupId}-${a.id}`));
      }
    });
    setArrRadios([...arrRadios]);
  };

  return (
    <div>
      {arrRadios &&
        arrRadios.map((radio) => (
          <RadioButton
            key={radio.id}
            groupId={props.groupId}
            id={radio.id}
            text={radio.text}
            checked={radio.checked}
            handleChange={handleChange}
          />
        ))}
    </div>
  );
}

function RadioButton(props) {
  const onDeselect = () => {
    console.log(`I am deselected ${props.groupId} : ${props.id}`);
  };

  useEffect(() => {
    window.addEventListener(
      `rb-deselect-${props.groupId}-${props.id}`,
      onDeselect,
      false
    );
    return () =>
      window.removeEventListener(
        `rb-deselect-${props.groupId}-${props.id}`,
        onDeselect,
        false
      );
  }, []);
  return (
    <>
      <input
        type="radio"
        name={props.groupId}
        value={props.id}
        onChange={props.handleChange}
        checked={props.checked}
      />
      <span>{props.text}</span>
    </>
  );
}

The idea is to hookup custom deselect event on the Radio button that fires (for all) when any other radio gets selected / changed.

Problem :

So I’ve created a radio component, and i have an onChange function run, but it doesn’t trigger when the radio is deselected by another radio with the same group name, why?

Heres my code for the radio component

import { useState } from 'react';

const RadioButton = (props) => {
    const [checked, setChecked] = useState((props.checked !== undefined) ? props.checked : false);

    const handleChange = () => {
        console.log('changed to: ' + checked);
        (checked) ? props.onChecked() : props.onUnchecked();
        setChecked(!checked);
    }

    return (
        <input type="radio" className={'border-grey custom-checkbox ' + (checked ? 'checked' : 'unchecked')} name={props.groupName} onChange={handleChange}/>
    )
}

export default RadioButton;

Comments

Comment posted by Mujeeb Qureshi

Yes, thats exactly how its supposed to work. Change is triggerd for the element that is clicked. If your use case needs you to list all other radios tat have been deselected, I would recommend trying to set a value to each radio and then in your change handler, try and incorparate the logic for selecting all deselected radios with something like this:

Comment posted by Agochar Bhatia

@MujeebQureshi could you elaborate further on how I would use e.target.value?

Comment posted by Mujeeb Qureshi

I see there has already been an answer added that should suffice. If you still want a approach that fires an event on deselect let me know.

Comment posted by Agochar Bhatia

@MujeebQureshi, if possible could you still show me, as the answer might me more implementable into my project. thanks

Comment posted by Agochar Bhatia

Would there be any way to add custom things such as labels or other elements in between each radio?

Comment posted by Chakib Salah

@AgocharBhatia Yes , I just edited my answer and you can find there how to do so , or you can check again the sandbox code .

Comment posted by Agochar Bhatia

Thank you so much for spending the time to answer this question! I truly appreciate it.

By