I’m working on a React form where I’m trying to change the background color of a label when a radio button is selected, but when I click/check the button it first update the state and on second click it shows me the background color.
I’m using a combination of onChange and checked attributes to manage the radio button state.
I have go through the Chat-GPT but it is still not answering me correctly
Below is the code for the Task component and CSS.
import React, { useState } from 'react';
export default function Task() {
let [formData, setformData] = useState({
uName: '',
uGender: '',
uMedals: 0,
});
let handleForm = (e) => {
let { name, value } = e.target;
e.preventDefault();
if (name === 'uMedals') {
value = parseInt(value);
if (value <= 0)
value = 0;
if (value > 20)
value = formData.uMedals;
}
setformData((formData) => ({
...formData,
[name]: value,
}));
};
return (
<div className='parent-container'>
<div className='content-wrapper'>
<div className='left'>
<form className='form-wrapper'>
<div className='name-wrapper'>
<label>Name</label>
{/* Name input */}
</div>
<div className='toogle-wrapper'>
<label className='lbl-gen'>Gender</label>
<div className='wrapper'>
<div className='custom-input'>
<input
type='radio'
id='female'
name='uGender'
value='female'
onChange={handleForm}
checked={formData.uGender === 'female'}
/>
<label htmlFor='female'>Female</label>
</div>
<div className='custom-input'>
<input
type='radio'
id='male'
name='uGender'
value='male'
onChange={handleForm}
checked={formData.uGender === 'male'}
/>
<label htmlFor='male'>Male</label>
</div>
</div>
</div>
<button style={{ width: '320px' }}>Add</button>
</form>
</div>
</div>
</div>
);
}
The below is the CSS code of above Task component
.custom-input input[type=radio] {
display: none;
}
.custom-input label {
display: block;
padding: 6px 8px;
color: #fff;
font-weight: bold;
text-align: center;
transition: all 0.4s ease;
cursor: pointer;
border-radius: 4px;
background-color: #717762;
}
.custom-input input[type='radio']:checked + label {
background-color: #f5f5f5;
color: #000;
}
You need to remove e.preventDefault()
from handleForm
function to change background color on first click.
function Task() {
const [formData, setformData] = React.useState({
uName: "",
uGender: "",
uMedals: 0,
});
const handleForm = (e) => {
let { name, value } = e.target;
if (name === "uMedals") {
value = parseInt(value);
if (value <= 0) value = 0;
if (value > 20) value = formData.uMedals;
}
setformData((formData) => ({
...formData,
[name]: value,
}));
};
return (
<div className="parent-container">
<div className="content-wrapper">
<div className="left">
<form className="form-wrapper">
<div className="name-wrapper">
<label>Name</label>
{/* Name input */}
</div>
<div className="toogle-wrapper">
<label className="lbl-gen">Gender</label>
<div className="wrapper">
<div className="custom-input">
<input
type="radio"
id="female"
name="uGender"
value="female"
onChange={handleForm}
checked={formData.uGender === "female"}
/>
<label htmlFor="female">Female</label>
</div>
<div className="custom-input">
<input
type="radio"
id="male"
name="uGender"
value="male"
onChange={handleForm}
checked={formData.uGender === "male"}
/>
<label htmlFor="male">Male</label>
</div>
</div>
</div>
<button style={{ width: "320px" }}>Add</button>
</form>
</div>
</div>
</div>
);
}
ReactDOM.render(<Task />, document.getElementById("root"));
.custom-input input[type=radio] {
display: none;
}
.custom-input label {
display: block;
padding: 6px 8px;
color: #fff;
font-weight: bold;
text-align: center;
transition: all 0.4s ease;
cursor: pointer;
border-radius: 4px;
background-color: #717762;
}
.custom-input input[type='radio']:checked + label {
background-color: #f5f5f5;
color: #000;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
3
In your CSS, you’re using the sibling combinator (+), which only applies styles to the that is immediately following the . This means that your CSS rule will only affect labels that are direct siblings of the radio inputs.
Keep the label immediately after the input within the same .custom-input container.
2
You are experiencing a conflict between React event system logic and the browser.
TLDR: Removing e.preventDefault()
solves your issue without any actual downsides.
In an attempt to normalize the change handling for radio buttons and checkboxes across multiple browsers, React hijacks the browser’s click event. Having a preventDefault
on the event causes the browser not to visually update the input but it doesn’t stop setting the state.
Setting a state triggers a re-render of the component. If you would add Checked: {formData.uGender}
somewhere, like below your button, you would see that it does re-render as the value is updated. The input isn’t updated however, since preventDefault
interfered.
Even though the first click didn’t show the visual effect, it did set the state. When clicking for the second time, it sets the state yet again and re-renders. Updating the visual effect is probably prevented this time as well, but as we already had the matching state from the previous click, the controlled input checked={formData.uGender === 'female'}
is now true and thus is shown visually as well.
According to MDN, the checked
attribute for checkboxes and radio buttons doesn’t actually reflect the selected value, but the initial default value. So there is a disconnect between the checked attribute, and whether or not the state of the input is checked or not. The conflict between the browser event logic and React’s event logic could be affected by this.
While debugging, I noticed that if you put a console.log in handleForm
it only prints for the first click.
This has been around since at least 2015. Given the comment in the source code, the need to normalize the change event to a click event was due to IE8. Since this is no longer a very common browser, one could assume that is would be safe to remove. The React team seems to be more prone to eventually removing the React event logic altogether in favor for a more mature browser event logic.
Either way, there is no need for the e.preventDefault()
in your case, since you do want the default behavior: to check the radio button.