I want to add "/" when user enters in a input field after dd/mm/yyyy, basically user is trying to enter DOB, and want '/' to be automatically added after entering dd then mm then yyyy. Also User wont be able to input more than 31 in dd & 12 in mm and for the year need to check if the DOB is invalid, in case of Leap year (example 29/02/2024) and not more than 4 digits.
I tried but when user enters text after 2 characters '/' is entered but the input is somehow lost. for example when user types '22' then '2', so only '22/' is shown not the 2.
Also i did tried the logic for the leap year but somehow managing the overall state in single input field is not working.
Here is what I tried.
import { useCallback, useState } from "react";
import moment from "moment";
import "./styles.css";
export default function App() {
const [dob, setDob] = useState("");
const [day, setDay] = useState("");
const [month, setMonth] = useState("");
const [year, setYear] = useState("");
const m = moment();
const [errors, setErrors] = useState();
const onchange = (e) => {
setDob(e.target.value);
checkAndAdd();
};
const handleDate = async () => {
let dayOfDob, monthOfDob, yearOfDob;
yearOfDob = dob.substring(6, 10);
if (dob.length === 2) {
dayOfDob = dob.substring(0, 2);
if (dayOfDob > 31) {
setErrors({ ...errors, dayO: "Day is greater" });
}
setDay(dayOfDob);
} else if (dob.length === 5) {
monthOfDob = dob.substring(3, 5);
if (monthOfDob > 12) {
setErrors({ ...errors, monthO: "Month is greater" });
}
setMonth(monthOfDob);
}
if (yearOfDob.length <= 4) {
let res = await isValidDate(dayOfDob, monthOfDob, yearOfDob);
console.log("validateDate", res);
}
};
const isValidDate = async (dayValue, monthValue, yearValue) => {
if (dayValue && monthValue && yearValue) {
let dayVal = dayValue.trim();
let monthVal = monthValue.trim();
const yearVal = yearValue.trim();
let leapYear = false;
if (dayVal != "" && monthVal != "" && yearVal != "") {
dayVal = dayVal <= 9 ? "0" Number(dayVal) : dayVal;
monthVal = monthVal <= 9 ? "0" Number(monthVal) : monthVal;
const d = new Date(yearVal "-" monthVal "-" dayVal);
if (yearVal % 4 !== 0 && monthVal == 2 && dayVal > 28) {
leapYear = true;
}
if (monthVal == 2 && dayVal > 29) {
leapYear = true;
}
if (yearVal.length === 4) {
if (!isNaN(d.getTime()) && !leapYear) {
setErrors("");
return d;
} else {
setErrors("DOB format is invalid");
return false;
}
}
} else {
return false;
}
}
};
const checkAndAdd = useCallback(() => {
if (dob.length === 2 || dob.length === 5) {
setDob(`${dob}/`);
handleDate();
}
}, [dob]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<input type="text" value={dob} onChange={onchange} />
</div>
);
}
Please let me know if explained it correctly. Also working sandbox is here
Thanks.
CodePudding user response:
To solve your problem of the number being replaced by a "/" you can simply do something like this on your onchange()
and checkAndAdd()
methods :
const onchange = (e) => {
const value = e.target.value;
setDob(value);
checkAndAdd(value);
};
const checkAndAdd = useCallback((value) => {
if (dob.length === 2 || dob.length === 5) {
const last = value.charAt(value.length - 1);
setDob(`${dob}/${last}`);
handleDate();
}
}, [dob]);
CodePudding user response:
So I just had to do this for a non-React legacy site using jQuery but the approach is easily adapted. Here is how to do it in jQuery (I don't have time at the moment to rewrite it but it's mostly vanilla JavaScript):
$("#dob").bind("keyup", function(event) {
var value = event.target.value.replace(/[^0-9\/]/g, "").replace(/\/\/ /g, '/').trim();
if (priorValue !== null && priorValue.length < event.target.value.length && value.length > 1) {
const parts = value.split("/");
const month = parts[0];
const day = parts[1];
const year = parts[2];
if (value.length === 2 && value.indexOf("/") === -1) {
// "10" -> "10/"
event.target.value = value "/";
} else if (value.length === 5 && parts.length !== 3) {
// "12/34" -> "12/34/"
event.target.value = value "/";
} else if (value.length === 4 && value.charAt(1) === "/" && !value.charAt(3) === "/") {
// "1/15" -> "1/15/"
// but not "1/1/" -> "1/1//"
event.target.value = value "/";
} else if (parts.length > 3 && value[value.length - 1] === "/") {
// "1/15/1950/" -> "1/15/1950"
event.target.value = value.slice(0, -1);
} else if (parts.length === 3 && year.length > 4) {
// "1/15/19501" -> "1/15/1950"
// avoiding slicing month and day w/ .slice(0, 2) for now...
event.target.value = month "/" day "/" year.slice(0, 4);
} else if (value !== event.target.value) {
event.target.value = value;
}
}
priorValue = event.target.value;
});
There are a bunch of gotchas like some event types not working on Android (although synthetic events in React might solve that for you). Also, I suggest setting HTML5 input hint to numeric on the field with inputmode="numeric"
which will default mobile to numeric input.
I check the length and if it's shorter than the prior length in order to handle backspace/deleting.
It's not perfect but it's working fairly well. I think there might be better methods like defaulting to a date input on desktop and doing an iOS-style date spinner but that is a fair amount of work (or you can pay for it, there are actually commercial solutions which seems excessive but there you go).