React не сразу обновляет State

Всем привет. Я новичок в веб-разработке. Не могу разобраться, почему событие «OnKeyDown» срабатывает со второго раза, а стейт соответственно обновляется не сразу. Моя задача - изменять (увеличить, уменьшить) части даты (день, месяц, год, час, минуты, секунды) в поле ввода с помощью нажатий на кнопки.

Функционал приложения:

  1. При нажатии комбинации кнопок 'ctrl' + 'ArrowUp' части даты увеличиваются с изменением других частей. Например, если текущая дата 31.12.2021 и вы измените дни с 31 на 01, дата изменится на 01.01.2022.
  2. При нажатии кнопки "ArrowUp", меняется только изменяемая часть, остальные части даты не изменяются.

Первый шаг функционала работает нормально. Моя проблема заключается в том что, когда я хочу увеличить дни или месяцы, нажав на кнопку «ArrowUp», стейт обновляется со второго раза, и это является причиной рассинхронизации. Надеюсь, я смог объяснить правильно.

запуск приложения через sandbox snippet

Проблемные части кода:

увеличиваем дни

if (e.key === "ArrowUp") {
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 2
      ) {
        e.preventDefault();
        console.log(new Date(milisec));
        let nextMilisec = milisec + 86400000;
        setMilisec(nextMilisec);
        let newDay = new Date(nextMilisec).getDate();
        console.log(newDay);
        setSelection({ start: 0, end: 2 });

        setDate((prevState) => ({
          ...prevState,
          day: newDay,
        }));
        console.log(day);
        cur_date.setDate(day);
        setValue(date_format_unmut);
      }

увеличиваем месяцы

 if (e.key === "ArrowDown") {
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 2
      ) {
        e.preventDefault();
        let nextMilisec = milisec - 86400000;
        setSelection({ start: 0, end: 2 });
        setMilisec(nextMilisec);
        setDate((prevState) => {
          return {
            ...prevState,
            day: new Date(nextMilisec).getDate(),
          };
        });
        cur_date.setDate(day);
        setValue(date_format_unmut);
      }
    }

Полный код (Заранее прошу прощения, я знаю, что его много, и мне нужен рефакторинг.)

import React, { useState, useEffect, useRef } from "react";
import styles from "./DataInput.module.css";

const cur_date = new Date();

const def_day = cur_date.getDate();
const def_month = cur_date.getMonth();
const def_year = cur_date.getFullYear();
const def_hour = cur_date.getHours();
const def_minute = cur_date.getMinutes();
const def_seconds = cur_date.getSeconds();

const dayNames = {
  0: "Sunday",
  1: "Monday",
  2: "Tuesday",
  3: "Wednesday",
  4: "Thursday",
  5: "Friday",
  6: "Saturday",
};

const monthNames = {
  0: "January",
  1: "February",
  2: "March",
  3: "April",
  4: "May",
  5: "June",
  6: "July",
  7: "August",
  8: "September",
  9: "October",
  10: "November",
  11: "December",
};

const DataInput = () => {
  const [milisec, setMilisec] = useState(cur_date.valueOf());
  const [date, setDate] = useState({
    day: def_day,
    month: def_month,
    year: def_year,
    hour: def_hour,
    minute: def_minute,
    second: def_seconds,
  });
  const { day, month, year, hour, minute, second } = date;

  const date_format_unmut = `${("0" + day).slice(-2)}/${
    monthNames[month - 1]
  }/${year} ${("0" + hour).slice(-2)}:${("0" + minute).slice(-2)}:${(
    "0" + second
  ).slice(-2)}`;

  const inputEl = useRef();
  const [selection, setSelection] = useState();

  const date_format = cur_date.toLocaleString().split(".");
  const monthIndex = +date_format[1];
  date_format[1] = monthNames[monthIndex - 1];
  let string_date_format = date_format.join("/").replace(",", "");

  const [value, setValue] = useState(string_date_format);

  useEffect(() => {
    if (!selection) return; // prevent running on start
    const { start, end } = selection;
    inputEl.current.focus();
    inputEl.current.setSelectionRange(start, end);
  }, [selection]);

  const keyHandler = (e) => {
    if (e.key === "ArrowUp") {
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 2
      ) {
        e.preventDefault();
        console.log(new Date(milisec));
        let nextMilisec = milisec + 86400000;
        setMilisec(nextMilisec);
        let newDay = new Date(nextMilisec).getDate();
        console.log(newDay);
        setSelection({ start: 0, end: 2 });

        setDate((prevState) => ({
          ...prevState,
          day: newDay,
        }));
        console.log(day);
        cur_date.setDate(day);
        setValue(date_format_unmut);
      }
      /* if (
        inputEl.current.selectionStart >= 3 &&
        inputEl.current.selectionStart <= date_format[1].length + 3
      ) {
        e.preventDefault();
        const newMonth = cur_date.getMonth() + 1;
        cur_date.setMonth(newMonth);
        setSelection({
          start: 3,
          end: date_format[1].length + 3,
        });
        setDate((prevState) => ({
          ...prevState,
          month: newMonth,
        }));
        console.log(month);
        setValue(date_format_unmut);
      } */
    }

    if (e.key === "ArrowUp" && e.getModifierState("Control")) {
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 1
      ) {
        e.preventDefault();
        setSelection({ start: 0, end: 2 });
        const newDay = cur_date.setDate(cur_date.getDate() + 1);
        const increaseDayData = new Date(newDay).toLocaleString().split(".");
        const monthIndex = +increaseDayData[1];
        increaseDayData[1] = monthNames[monthIndex - 1];
        const addDay_date_format = increaseDayData.join("/").replace(",", "");
        console.log(addDay_date_format);
        setValue(addDay_date_format);
      }
      if (
        inputEl.current.selectionStart >= 3 &&
        inputEl.current.selectionStart <= date_format[1].length + 3
      ) {
        const newMonth = cur_date.setMonth(cur_date.getMonth() + 1);
        const increaseMonthData = new Date(newMonth)
          .toLocaleString()
          .split(".");
        const monthNumber = increaseMonthData[1];
        setSelection({ start: 3, end: monthNames[monthNumber - 1].length + 3 });
        const monthIndex = +increaseMonthData[1];
        increaseMonthData[1] = monthNames[monthIndex - 1];
        const addMonth_date_format = increaseMonthData
          .join("/")
          .replace(",", "");
        setValue(addMonth_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 4 &&
        inputEl.current.selectionStart <= date_format[1].length + 8
      ) {
        const newYear = cur_date.setFullYear(cur_date.getFullYear() + 1);
        const increaseYearData = new Date(newYear).toLocaleString().split(".");
        setSelection({
          start: date_format[1].length + 4,
          end: date_format[1].length + 8,
        });
        const monthIndex = +increaseYearData[1];
        increaseYearData[1] = monthNames[monthIndex - 1];
        const addYear_date_format = increaseYearData.join("/").replace(",", "");
        setValue(addYear_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 9 &&
        inputEl.current.selectionStart <= date_format[1].length + 11
      ) {
        const newHour = cur_date.setHours(cur_date.getHours() + 1);
        const increaseHourData = new Date(newHour).toLocaleString().split(".");
        setSelection({
          start: date_format[1].length + 9,
          end: date_format[1].length + 11,
        });
        const monthIndex = +increaseHourData[1];
        increaseHourData[1] = monthNames[monthIndex - 1];
        const addHour_date_format = increaseHourData.join("/").replace(",", "");
        setValue(addHour_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 12 &&
        inputEl.current.selectionStart <= date_format[1].length + 14
      ) {
        const newMinutes = cur_date.setMinutes(cur_date.getMinutes() + 1);
        const increaseMinutesData = new Date(newMinutes)
          .toLocaleString()
          .split(".");
        setSelection({
          start: date_format[1].length + 12,
          end: date_format[1].length + 14,
        });
        const monthIndex = +increaseMinutesData[1];
        increaseMinutesData[1] = monthNames[monthIndex - 1];
        const addMinutes_date_format = increaseMinutesData
          .join("/")
          .replace(",", "");
        setValue(addMinutes_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 15 &&
        inputEl.current.selectionStart <= date_format[1].length + 17
      ) {
        const newSeconds = cur_date.setSeconds(cur_date.getSeconds() + 1);
        const increaseSecondsData = new Date(newSeconds)
          .toLocaleString()
          .split(".");
        setSelection({
          start: date_format[1].length + 15,
          end: date_format[1].length + 17,
        });
        const monthIndex = +increaseSecondsData[1];
        increaseSecondsData[1] = monthNames[monthIndex - 1];
        const addSeconds_date_format = increaseSecondsData
          .join("/")
          .replace(",", "");
        setValue(addSeconds_date_format);
      }
    }
    if (e.key === "ArrowDown") {
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 2
      ) {
        e.preventDefault();
        let nextMilisec = milisec - 86400000;
        setSelection({ start: 0, end: 2 });
        setMilisec(nextMilisec);
        setDate((prevState) => {
          return {
            ...prevState,
            day: new Date(nextMilisec).getDate(),
          };
        });
        cur_date.setDate(day);
        setValue(date_format_unmut);
      }
    }
    if (e.key === "ArrowDown" && e.getModifierState("Control")) {
      e.preventDefault();
      if (
        inputEl.current.selectionStart >= 0 &&
        inputEl.current.selectionStart <= 1
      ) {
        setSelection({ start: 0, end: 2 });
        const newDay = cur_date.setDate(cur_date.getDate() - 1);
        const increaseDayData = new Date(newDay).toLocaleString().split(".");
        const monthIndex = +increaseDayData[1];
        increaseDayData[1] = monthNames[monthIndex - 1];
        const addDay_date_format = increaseDayData.join("/").replace(",", "");
        setValue(addDay_date_format);
      }
      if (
        inputEl.current.selectionStart >= 3 &&
        inputEl.current.selectionStart <= date_format[1].length + 3
      ) {
        const newMonth = cur_date.setMonth(cur_date.getMonth() - 1);
        const increaseMonthData = new Date(newMonth)
          .toLocaleString()
          .split(".");
        const monthNumber = increaseMonthData[1];
        setSelection({ start: 3, end: monthNames[monthNumber - 1].length + 3 });
        const monthIndex = +increaseMonthData[1];
        increaseMonthData[1] = monthNames[monthIndex - 1];
        const addMonth_date_format = increaseMonthData
          .join("/")
          .replace(",", "");
        setValue(addMonth_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 4 &&
        inputEl.current.selectionStart <= date_format[1].length + 8
      ) {
        const newYear = cur_date.setFullYear(cur_date.getFullYear() - 1);
        const increaseYearData = new Date(newYear).toLocaleString().split(".");
        setSelection({
          start: date_format[1].length + 4,
          end: date_format[1].length + 8,
        });
        const monthIndex = +increaseYearData[1];
        increaseYearData[1] = monthNames[monthIndex - 1];
        const addYear_date_format = increaseYearData.join("/").replace(",", "");
        setValue(addYear_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 9 &&
        inputEl.current.selectionStart <= date_format[1].length + 11
      ) {
        const newHour = cur_date.setHours(cur_date.getHours() - 1);
        const increaseHourData = new Date(newHour).toLocaleString().split(".");
        setSelection({
          start: date_format[1].length + 9,
          end: date_format[1].length + 11,
        });
        const monthIndex = +increaseHourData[1];
        increaseHourData[1] = monthNames[monthIndex - 1];
        const addHour_date_format = increaseHourData.join("/").replace(",", "");
        setValue(addHour_date_format);
      }

      if (
        inputEl.current.selectionStart >= date_format[1].length + 12 &&
        inputEl.current.selectionStart <= date_format[1].length + 14
      ) {
        const newMinutes = cur_date.setMinutes(cur_date.getMinutes() - 1);
        const increaseMinutesData = new Date(newMinutes)
          .toLocaleString()
          .split(".");
        setSelection({
          start: date_format[1].length + 12,
          end: date_format[1].length + 14,
        });
        const monthIndex = +increaseMinutesData[1];
        increaseMinutesData[1] = monthNames[monthIndex - 1];
        const addMinutes_date_format = increaseMinutesData
          .join("/")
          .replace(",", "");
        setValue(addMinutes_date_format);
      }
      if (
        inputEl.current.selectionStart >= date_format[1].length + 15 &&
        inputEl.current.selectionStart <= date_format[1].length + 17
      ) {
        const newSeconds = cur_date.setSeconds(cur_date.getSeconds() - 1);
        const increaseSecondsData = new Date(newSeconds)
          .toLocaleString()
          .split(".");
        setSelection({
          start: date_format[1].length + 15,
          end: date_format[1].length + 17,
        });
        const monthIndex = +increaseSecondsData[1];
        increaseSecondsData[1] = monthNames[monthIndex - 1];
        const addSeconds_date_format = increaseSecondsData
          .join("/")
          .replace(",", "");
        setValue(addSeconds_date_format);
      }
    }
  };

  const fullDateScreen = cur_date.toLocaleString().split(" ");
  const timeScreen = fullDateScreen[1];
  const dateScreen = fullDateScreen[0];
  const dayScreen = dayNames[cur_date.getDay()];
  const monthScreen = monthNames[cur_date.getMonth()];
  const dateScreenFormat = dateScreen.replaceAll(".", " ");
  const firstNumber = dateScreenFormat[0];
  const smallDateFormat =
    dayScreen + "," + " " + dateScreenFormat.slice(1, -8) + " " + monthScreen;
  const bigDateFormat =
    dayScreen + "," + " " + dateScreenFormat.slice(0, -8) + " " + monthScreen;

  const changeHandler = (e) => {
    setValue(e.target.value);
  };

  return (
    <div className={styles.content}>
      <div className={styles.inputDublicat}>
        <h1>{timeScreen.slice(0, 5)}</h1>
        <p>{firstNumber == 0 ? smallDateFormat : bigDateFormat}</p>
      </div>
      <div className={styles.main}>
        <div>
          <h1 className={styles.title}> Frontend Task</h1>
          <input
            ref={inputEl}
            value={value}
            onChange={changeHandler}
            onKeyDown={keyHandler}
          />

          <div className={styles.textBlock}>
            <div className={styles.instruction}>
              <p>
                You can change input parts of date
                (day,month,year,hour,minutes,seconds) by pressing buttons or
                combination. The cursor must be on the focus in the input field.
              </p>
            </div>
            <div>
              <h3>Buttons </h3>
              <p>&uarr; - unmutable date</p>
              <p>&darr; - unmutable date</p>
            </div>
            <div>
              <h3>Combinations</h3>
              <p>"CTRL" + &uarr; - mutable date</p>
              <p>"CTRL" + &darr; - unmutable date</p>
            </div>
          </div>
        </div>
      </div>
      <div>
        <p className={styles.name}>DAVID ABRAMOV</p>
        <hr width="1200px" className={styles.line} />
      </div>
    </div>
  );
};

export default DataInput;

Ответы (0 шт):