I tried to change a display color from Blue to Orange when the temperature > 30'C or > 86'F by using a toggle switch and a range slider. the toggle switch is working ok, but the range slider doesn't work correctly.
For example,
When move the range slider to 87'F, sometimes the display is Blue (it's supposed to be Orange), and sometimes when move the range slider back to 86'F, the display = Orange (it's supposed to be Blue).
I wonder why sometimes it works, sometimes it doesn't work. Code URL
const App = () => {
const [value, setValue] = React.useState(0);
const [toggle, setToggle] = React.useState(false);
const [displayColor, setDisplayColor] = React.useState('cold');
const convertTemp = () => {
if(toggle){
setValue(value*9/5 32);
if ( value > 30){
setDisplayColor('hot');
}
else {
setDisplayColor('cold');
}
}
else if (!toggle){
setValue((value-32)*5/9);
if ( value > 86){
setDisplayColor('hot');
}
else {
setDisplayColor('cold');
}
}
}
const changeTemp = (e) => {
setValue(e.target.value);
if(toggle){
if ( value > 30){
setDisplayColor('hot');
}
else {
setDisplayColor('cold');
}
}
else if (!toggle) {
if ( value > 86){
setDisplayColor('hot');
}
else {
setDisplayColor('cold');
}
}
}
return (
<div className="container">
<div className={`display ${displayColor}`}>
{Math.round(value)}
</div>
<h1>{toggle ? "Celsius" : 'Fahrenheit'}</h1>
<label className='switch'>
<input onClick={convertTemp} onChange={(e) => setToggle(e.target.checked)} className='toggle' type='checkbox'></input>
<span className='slider'></span>
</label>
<input onChange={changeTemp} type="range" min="-100" max="100"></input>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
Here is CSS
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background-color: rgba(198, 208, 211, 0.922);
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
width: 250px;
height: 400px;
display: grid;
align-items: center;
justify-content: center;
border-radius: 50px;
}
.display {
border-radius: 50px;
font-size: 60px;
height: 100px;
width: 200px;
display: flex;
align-items: center;
justify-content: center;
margin: 10px 0;
}
.switch {
position: relative;
display: inline;
width: 60px;
height: 34px;
margin-left: 70px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgb(91, 104, 150);
transition: 0.4s;
}
.slider:before {
position: absolute;
content: '';
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: aliceblue;
transition: 0.4s;
}
input:checked .slider {
background-color: rgb(221, 92, 92);
}
input:checked .slider:before {
transform: translateX(26px);
}
h1 {
display: flex;
align-items: center;
justify-content: center;
}
input[type=range] {
height: 30px;
margin: 10px 0;
cursor: pointer;
}
.cold {
background-color: rgb(95, 161, 201);
}
.hot {
background-color: rgb(242, 153, 101);
}
CodePudding user response:
There is no need to set a state for bg color since it is already depending on another states.
Instead do as normal const:
const bgColor = (toggle && value > 30) || (!toggle && value > 86) ? 'hot' : 'cold'
And insert into the styles:
<div className={`display ${bgColor}`}>
{Math.round(value)}
</div>
This should avoid occasional conflict in syncing and solve the issue.
CodePudding user response:
Based on @John Li's answer this is your final code. I have also done some code readablity changes.
const App = () => {
const [value, setValue] = React.useState(0);
const [isCelsius, setIsCelsius] = React.useState(false);
const bgColor = (isCelsius && value > 30) || (!isCelsius && value > 86) ? 'hot' : 'cold'
return (
<div className="container">
<div className={`display ${bgColor}`}>
{Math.round(value)}
</div>
<h1>{isCelsius ? "Celsius" : 'Fahrenheit'}</h1>
<label className='switch'>
<input onChange={(e) => {
setIsCelsius(e.target.checked)
}}
className='toggle'
type='checkbox'
/>
<span className='slider'></span>
</label>
<input
onChange={(e) => setValue(e.target.value)}
type="range"
min="-100"
max="100"
/>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
To recap the reason as of why this was happening.
setValue(value*9/5 32);
if ( value > 30){
setDisplayColor('hot');
}
else {
setDisplayColor('cold');
}
In above piece of code, you set a state value and in that same function you run a logic on just changed state value. Even tho state change is really fast, it is still an asynchonous operation and it tends to happen, that the code that should seemingly execute after the state changes, executes before it does. So every time you setState and run function on the value of your changed state right after, pass that value as function parameter.