i'm following a React course on udemy, and while doing a project to test my knowledge about state, i got into a bug i can't solve. The project is about coding an application that has three state variable (count,step,date) and when count changes, the date should also be changed.
Count range from -infinity to +infinity Step range from 1 to +infinity. Date is the Date Object.
Now, when i go to change the count variable for the first time, it doesn't update the date. The second time it does, but it changes based on the value of the previous event.
E.g:
First Event
Count: 0->1
Date: 2 December 2023 -> 2 December 2023
Second Event
Count: 1->2
Date: 2 December 2023 -> 3 December 2023
`
Third Event
Count: 2->1
Date: 3 December 2023 -> 4 December 2023
Here's the code:
export default function App() {
const [count, setCount] = useState(0);
const [date,setDate]= useState(new Date(Date.now() + (count*86400*1000)).toDateString());
const handlePrevCount = () => {
setCount((prev) => prev - 1);
setDate((prev) => new Date(Date.now() + (count*86400*1000)).toDateString())
};
const handleNextCount = () => {
setCount((prev) => prev + 1);
setDate((prev) => new Date(Date.now() + (count*86400*1000)).toDateString())
};
return (
<div className="App">
<div>
<button onClick={handlePrevCount}>-</button>
<button disabled>Count: {count}</button>
<button onClick={handleNextCount}>+</button>
</div>
<span>{count === 0 && <p>Today is ${date}</p>}</span>
<span>{count >= 1 && <p>{count} days from now will be {date}</p>}</span>
<span>{count < 0 && <p>{Math.abs(count)} days ago was {date}</p>}</span>
</div>
);
}
This is because react updates state variables in batches. In other words, doing setCount(...) doesn't guarantee that state variable count
will be updated immediately and be available while executing next line (i.e. in setDate(...)). React does this to improve performance by avoiding frequent re rendering due to each state variable change. You can read more about it here in official docs
So I will suggest to do something like -
const handlePrevCount = () => {
let latestCount = count;
setCount((prev) => {
latestCount = (prev - 1);
return latestCount;
});
setDate((prev) => new Date(Date.now() + (latestCount*86400*1000)).toDateString())
};
OR
Ideally it should be done using useEffect like this -
export default function App() {
const [count, setCount] = useState(0);
const [date,setDate]= useState(new Date(Date.now() + (count*86400*1000)).toDateString());
useEffect(() => {
setDate((new Date(Date.now() + (count*86400*1000))).toDateString())
}, [count])
const handlePrevCount = () => setCount((prev) => prev - 1);
const handleNextCount = () => setCount((prev) => prev + 1);
return (
<div className="App">
<div>
<button onClick={handlePrevCount}>-</button>
<button disabled>Count: {count}</button>
<button onClick={handleNextCount}>+</button>
</div>
<span>{count === 0 && <p>Today is ${date}</p>}</span>
<span>{count >= 1 && <p>{count} days from now will be {date}</p>}</span>
<span>{count < 0 && <p>{Math.abs(count)} days ago was {date}</p>}</span>
</div>
);
}