Saif Khadraoui
Saif Khadraoui

Reputation: 119

Why does my counter value increase before it starts to decrease on click - reactjs

I'm making an attendance form where teachers can mark who's absent and not and check who was absent the previous days. I have the current date at the top and button which allows it to change the date forward and back. However, when I try to increase the value it works fine, but then when I decrease it increases by one first and then decreases after every click.

 const [date, setDate] = useState()

  var today = new Date();
    var dd = parseInt(String(today.getDate()).padStart(2, '0'));
    var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
    var yyyy = today.getFullYear(); 
    
    today = dd + '/' + mm + '/' + yyyy;
    useEffect(() => {
      setDate(today)

    }, [])

const[newDay, setNewDay] = useState(dd)
  
  const changeToTomorrow = () => {
    setNewDay(newDay + 1)
    setDate(newDay + '/' + mm + '/' + yyyy)
    console.log(date)
  }

  const changeToYesterday = () => {
    setNewDay(newDay - 1)
    setDate(newDay + '/' + mm + '/' + yyyy)
  }


    return (
        <div className="attendance-today-container">
          <h1>Daily attendance</h1>
          <div className='change-date-container'>
            <div className='change-date'>
              <i class="fas fa-arrow-left" onClick={changeToYesterday}></i>
              <p>{date}</p>
              <i class="fas fa-arrow-right" onClick={changeToTomorrow}></i>
            </div>
          </div>

Upvotes: 2

Views: 534

Answers (3)

secan
secan

Reputation: 2679

May I suggest a different approach, as well as a different organization of the code?

const { useState, useEffect, useCallback } = React;

// utility function (not part of the component)
function formatDate(date, format = 'dd/mm/yyyy') {
  const addLeadingZero = num => {
    return num < 10 ? `0${num}` : num.toString();
  }

  const dd = addLeadingZero(date.getDate());
  const mm = addLeadingZero(date.getMonth() + 1);
  const yyyy = date.getFullYear().toString();

  return format.replace('dd', dd).replace('mm', mm).replace('yyyy', yyyy);
}

// This constant is not strictly necessary but I think it makes the code more readable
const ONE_DAY_MSEC = 86400000;

// Component
const MyComponent = () => {
  const [date, setDate] = useState(new Date()); // date as a date
  const [dispDate, setDispDate] = useState(formatDate(new Date())); // date as a formatted string
  
  useEffect(() => {
    setDispDate(formatDate(date))
  }, [date, setDispDate]);
  
  const handleNextDayClick = useCallback(() => {
    setDate((current) => new Date(current.getTime() + ONE_DAY_MSEC))
  }, [setDate]);
  
  const handlePrevDayClick = useCallback(() => {
    setDate((current) => new Date(current.getTime() - ONE_DAY_MSEC))
  }, [setDate]);
  
  return (
    <div>
      <div>Date: {dispDate}</div>
      <button onClick={handlePrevDayClick}>prev day</button>
      <button onClick={handleNextDayClick}>next day</button>
    </div>
  );
}

ReactDOM.render(<MyComponent />, document.getElementById('App'));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="App" />

Upvotes: 0

AleksandrSl
AleksandrSl

Reputation: 46

When you update a state using setNewDay(newDay + 1) the newDay variable is not immediately updated. So when you do setDate(newDay + '/' + mm + '/' + yyyy) you still use the old value of newDay to update date.

Example:

You have initial state newDay = 10 date = 10/xx/xx, then you click on increase button and the next code is executed. setNewDay(10 + 1) setDate(10 + '/' + mm + '/' + yyyy)

After react renders component with a new state you got newDay = 11 date = 10/xx/xx on the next click to increase you would get setNewDay(11 + 1) setDate(11 + '/' + mm + '/' + yyyy)

So you date variable update is always one step behind.

How to fix

You should do something like

let nextDay = newDay + 1
setNewDay(nextDay)
setDate(nextDay + '/' + mm + '/' + yyyy)

in both functions

Upvotes: 2

Ben
Ben

Reputation: 668

You are trying to set two states in a row while those are asynchronous and might not give you the updated value of newDay by the time you set date.

A fast fix can be as follows (I rearrange the code a bit with spacing):

import {useState, useEffect} from "react";

export default function SomeComponent() {
  
  const [date, setDate] = useState()

  var today = new Date();
  var dd = parseInt(String(today.getDate()).padStart(2, '0'));
  var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
  var yyyy = today.getFullYear(); 
  
  today = dd + '/' + mm + '/' + yyyy;
  useEffect(() => {
    setDate(today)

  }, [])

  const[newDay, setNewDay] = useState(dd)

  const changeToTomorrow = () => {
    const updateDay = newDay + 1
    setNewDay(updateDay)
    setDate(updateDay + '/' + mm + '/' + yyyy)
    console.log(date)
  }

  const changeToYesterday = () => {
    const updateDay = newDay - 1
    setNewDay(updateDay)
    setDate(updateDay + '/' + mm + '/' + yyyy)
  }


  return (
      <div className="attendance-today-container">
        <h1>Daily attendance</h1>
        <div className='change-date-container'>
          <div className='change-date'>
            <i class="fas fa-arrow-left" onClick={changeToYesterday}></i>
            <p>{date}</p>
            <i class="fas fa-arrow-right" onClick={changeToTomorrow}></i>
          </div>
        </div>
      </div>
  );
}

Calculate the updated date first in a new variable and then set it to both states:

  const changeToTomorrow = () => {
    const updateDay = newDay + 1
    setNewDay(updateDay)
    setDate(updateDay + '/' + mm + '/' + yyyy)
    console.log(date)
  }

  const changeToYesterday = () => {
    const updateDay = newDay - 1
    setNewDay(updateDay)
    setDate(updateDay + '/' + mm + '/' + yyyy)
  }

Upvotes: 1

Related Questions