React не сразу обновляет State
Всем привет. Я новичок в веб-разработке. Не могу разобраться, почему событие «OnKeyDown» срабатывает со второго раза, а стейт соответственно обновляется не сразу. Моя задача - изменять (увеличить, уменьшить) части даты (день, месяц, год, час, минуты, секунды) в поле ввода с помощью нажатий на кнопки.
Функционал приложения:
- При нажатии комбинации кнопок 'ctrl' + 'ArrowUp' части даты увеличиваются с изменением других частей. Например, если текущая дата 31.12.2021 и вы измените дни с 31 на 01, дата изменится на 01.01.2022.
- При нажатии кнопки "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>↑ - unmutable date</p>
<p>↓ - unmutable date</p>
</div>
<div>
<h3>Combinations</h3>
<p>"CTRL" + ↑ - mutable date</p>
<p>"CTRL" + ↓ - 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;