Search code examples
reactjsdateobjectstate

React State updates only from the second click event


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>
      );
     }

Solution

  • 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>
      );
     }