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;
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
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.