I am currently working on an input label, which formats and fixes an user input to a correct date-time format.
Currently, nothing except digits will be formatted to a date.
For instance: 11302020 => 11 / 30 / 2020
Now I want to set a range the string parts for day, month, year.
If a user exceeds the limit, the number (or part of the string) will be sanitized.
My function chops the input string into chunks, so I can write the values into a new array.
However, at the end my chopped array has a) size of 6 and b) an overall char length with blanks of 15. When I put these conditions in an if-question to save these values in separate parts, it starts saving at a char length of 16, which means, after an user enters the full date and an additional char, which is not what I want with my (b). Can someone help me out?
import React, { useState } from "react";
// export const dateFormatter = (input) => {
// return input;
// };
export default function App() {
const [maskedState, setMaskedState] = useState("");
const dateFormatter = (date) => {
setMaskedState(date.replace(/\D/g, "")
.replace(/(\d{2})(\d)/, " $1 / $2")
.replace(/(\d{2})(\d)/, "$1 / $2")
.replace(/(\d{4})\d+?$/, "$1"));
const maskedStateStr = maskedState.split(" ");
const charLength = 15;
const arrLength = 6;
if ((maskedStateStr.length === arrLength) && (maskedState.length === charLength)){
maskedStateStr.shift();
var day = maskedStateStr[0];
var month = maskedStateStr[2];
var year = maskedStateStr[4];
console.log(day,month,year);
}
//console.log(maskedStateStr, maskedStateStr.length, maskedState, maskedState.length)
}
const handleInput = (date) => {
const inputValue = date.target.value;
dateFormatter(inputValue);
};
return (
<div className="App">
<input onChange={handleInput} value={maskedState} />
</div>
);
}
There are so many issues with that code but I only focus on react part of it.
setMaskedState doesn't update maskedState immediately so maskedState will most likely point to a stale state.
setting value on an input renders it uneditable so you don't even see what you're typing. Use defaultValue.
That said you should operate on the date value and only set the state at the end of your block to reflect the result. Like:
export default function App() {
const [maskedState, setMaskedState] = useState(null);
const dateFormatter = value => {
let formattedDate = value
.replace(/\D/g, "")
.replace(/(\d{2})(\d)/, " $1 / $2")
.replace(/(\d{2})(\d)/, "$1 / $2")
.replace(/(\d{4})\d+?$/, "$1");
const maskedStateStr = formattedDate.split(" ");
const charLength = 15;
const arrLength = 6;
if (
maskedStateStr.length === arrLength &&
formattedDate.length === charLength
) {
maskedStateStr.shift();
let day = maskedStateStr[0];
let month = maskedStateStr[2];
let year = maskedStateStr[4];
setMaskedState(`${day} / ${month} / ${year}`);
} else {
setMaskedState(value);
}
};
const handleInput = date => {
const inputValue = date.target.value;
dateFormatter(inputValue);
};
return (
<div className="App">
<input onChange={handleInput} value={maskedState} />
<pre>
<code>
{maskedState
? JSON.stringify(maskedState, null, 2)
: "Not a valid date yet"}
</code>
</pre>
</div>
);
}
See the demo on StackBlitz
Related
Below is a function for joining words. Given the first or last word could be a string like 7 1/2, how would I ensure that; if the word contains a fraction, format the fraction with (superscript) tags.. so it shows nicely like below?
export const joinWords = (words: string[]): string => {
if (words.length === 1) {
return words[0];
}
const firstWords = words.slice(0, words.length - 1);
const lastWord = words[words.length - 1];
const oxfordComma = words.length > 2;
//if firstWords or lastword contains "/"...Logic???
return firstWords.join(', ') + `${oxfordComma ? ', ' : ''} and ${lastWord}`;
};
I found an alternate way, where I use the replace method. I am using svelte hence the onmount..
onMount(() => {
//join the words
const test = joinWords(optionNames);
//replace the "1/2" since I am only using 1/2 for clothing sizes...this may not work for other fractions..
const newTest = test.replace('1/2', '½');
testWord = newTest;
});
//be sure to return as html...again im in svelte.
<span>{#html testWord} </span><br />
There is an input where the user must enter a date in the format dd.mm.yyyy., And I need to check that he does not write letters and other unnecessary characters. The Internet is full of examples for a ready-made date, in my case it is necessary to check as you enter. For example, the user pressed the key 2 - it appeared in the input, pressed z - nothing changed in the input. Pressed 3 - ok, pressed / - nothing has changed. I tried to write a regular expression,
((0[1-9]{0,1})|([1-2][0-9]{0,1})|(3[0-1]{0,1}))\.{ 0.1}
but I can't figure out how to check the data after the dot. Need your advice.
Here is a small example
Just updated you tags on question, as your linked example was using React, and how you do this in plain JS would be slightly different.
Rather than regEx, a slightly easier option might be to use lookups, you could then check what valid chars are available at each char position.
Below is an example.
ps. It won't catch everything, eg. 31 days in Feb, & 29/28 for leap years etc. So ideally you should check again after date fully entered.
const {useState} = React;
const anynum = '01234567890';
const num01 = '01';
const num31 = '0123';
const dot = '.';
const posValid = [
num31, //3
anynum, //1
dot, //.
num01, //1
anynum, //2
dot, //.
anynum, //2
anynum, //0
anynum, //2
anynum //2
]
function partsValid(s) {
if (s.length > posValid.length) return false;
//simple num check at pos
for (let l = 0; l < s.length; l += 1) {
const c = s[l];
if (!posValid[l].includes(c)) return false;
}
//check day
if (s.length > 1) {
const day = parseInt(s.slice(0, 2), 10);
if (!(day >= 1 && day <= 31)) return false;
}
//check month
if (s.length > 4) {
const month = parseInt(s.slice(3, 5), 10);
if (!(month >= 1 && month <= 12)) return false;
}
return true;
}
function App() {
const [value, setValue] = useState("");
const onchange = (e) => {
const nv = e.target.value;
if (partsValid(nv)) setValue(nv);
};
return (
<div>
<input onChange={onchange} value={value} placeholder="dd.mm.yyyy" />
</div>
);
}
ReactDOM.render(<App/>, document.querySelector('#mount'));
<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="mount"></div>
I am trying to replace 4 numbers with a time format. For example if user enters 1234 to be replaced with 12:34
I have found that this regex does this job
let myString = "1234";
myString.replace(/\b(\d{2})(\d{2})/g, '$1:$2')
But now I am trying to figure out how to use this with cases like
94 - this should be replaced with 23 then it does same for time after the colon
01:74 - this should be replaced with 01:59
I have found a regex which does that ^([0-1]?[0-9]|2[0-3]):[0-5][0-9], but I am not able to figure out how to combine this with .replace
You will need to match on the first and second pair of digits and then bound them by their max values. Once you have the bounded values, you can pad the numbers and join them with a colon.
const toTimeString = (value) => {
const
[, hours, minutes] = value.match(/^(\d{2})(\d{2})$/),
hour = `${Math.min(+hours, 24)}`.padStart(2, '0'),
minute = `${Math.min(+minutes, 59)}`.padStart(2, '0');
return `${hour}:${minute}`;
};
console.log(toTimeString('0174')); // 01:59
console.log(toTimeString('3412')); // 24:12
Now, here is a replacer example:
const minPad = (value, min) => `${Math.min(value, min)}`.padStart(2, '0');
const toTimeString = (value) =>
value.replace(/\b(\d{2})(\d{2})\b/g, (match, hours, minutes) =>
`${minPad(hours, 24)}:${minPad(minutes, 59)}`);
console.log(toTimeString('0174 3412')); // 01:59 24:12
There is an overload of replace which takes a function which you can use to do any logic you need, such as:
let myString = "1234";
function formatTime(input){
return input.replace(/\b(\d{2})(\d{2})/, (_,hh,mm) => {
return `${Math.min(hh,24)}:${Math.min(mm,59)}`
})
}
console.log(formatTime("1234"));
console.log(formatTime("3412"));
console.log(formatTime("0174"));
I do not see any good reason to use regex in your case.
Just a simple function will do the job.
function transformTextToHHMMTimeFormat(text) {
const firstNumber = Number(text.slice(0, 2))
const secondNumber = Number(text.slice(2, 4))
const hour = Math.min(firstNumber, 24)
const minute = Math.min(secondNumber, 59)
return `${hour}:${minute}`
}
I am using React for my frontend app.
I have two different time format of data.
One is like this 08-10 and another one is like this 05:00-05:30.
Most of the time format data is like this 08-10, few are like 05:00-05:30.
After getting the time date data, I used map function and pass to my time-format helper function, In my browser I want to display my data like this 05:00-05:30.
My helper function do like this: if the time looks like this 08-10 then the function will split it into two then add : and convert them into 08:00-10:00. As I mentioned I have two different time format data, when the data come like this 05:00-05:30 then my helper function convert them like 0500-0530.
I want to render my function conditionally if the data is like 05:00-05:30 then return as it is, if the data is like this 08-10 then convert them into 08:00-10:00. I don't know how to do that in my helper function.
const toTimeRangeFormat = (range) => {
console.log(range);
const [start, end] = range?.split("-");
const toFourDigitTime = (time) => {
const [hours, minutes] = time.split(":");
return hours.padStart(2, "0") + (minutes ? minutes : ":00");
};
if (start && end) {
return toFourDigitTime(start) + " - " + toFourDigitTime(end);
}
return range;
};
const time = ["08-10", "05:00-05:30"];
time.filter((i) => {
if (typeof i === "string") {
return toTimeRangeFormat(i);
}
});
console.log(toTimeRangeFormat());
Your code seemed to work if you call it correctly
I assume you want this though
const re = /(\d{2}):?(\d{2})?/; // take the (set of) two digits from NN:NN, NNNN or NN - the ? means optional
const toFourDigitTime = time => {
const [hours, minutes] = time.match(re).slice(1); // ignore result[0]
return `${hours.padStart(2, "0")}:${minutes ? minutes : "00"}`;
};
const toTimeRangeFormat = (range) => {
const [start, end] = range ?.split("-");
if (start && end) {
return toFourDigitTime(start) + " - " + toFourDigitTime(end);
}
return range;
};
const time = ["08-10", "05:00-05:30"];
const time1 = time.map(str => toTimeRangeFormat(str));
console.log(time1);
I'm trying to make credit/debit card form with React. I want to make autoinsert space after every 4 digit and to force the user type numbers only. For example, if input 1234567891234567 it will become with spaces 1234 5678 9123 4567.
The problem is when I press backspace the spaces can't be deleted because I use onChange event. I'm new to React, probably need a different approach. What else I can use to make the backspace working properly and how to forbid writing of letters (digits only)?
const [cardNumber, setCardNumber] = useState('')
const handleChange = async (e) => {
if (e.target.value.length === 4) {
e.target.value += ' '
} else if (e.target.value.length === 9) {
e.target.value += ' '
} else if (e.target.value.length === 14) {
e.target.value += ' '
}
setCardNumber(e.target.value)
}
Id create a state variable to hold the cc-number
const [ccNumber, setCcNumber] = useState("");
And set the ccNumber onChange of the input
<input type="text" value={ccNumber} onChange={formatAndSetCcNumber} />
And inside fn formatAndSetCcNumber I'd handle the logic
const formatAndSetCcNumber = e => {
const inputVal = e.target.value.replace(/ /g, ""); //remove all the empty spaces in the input
let inputNumbersOnly = inputVal.replace(/\D/g, ""); // Get only digits
if (inputNumbersOnly.length > 16) {
//If entered value has a length greater than 16 then take only the first 16 digits
inputNumbersOnly = inputNumbersOnly.substr(0, 16);
}
// Get nd array of 4 digits per an element EX: ["4242", "4242", ...]
const splits = inputNumbersOnly.match(/.{1,4}/g);
let spacedNumber = "";
if (splits) {
spacedNumber = splits.join(" "); // Join all the splits with an empty space
}
setCcNumber(spacedNumber); // Set the new CC number
};
Here is a demo
Try this:
const handleChange = async (e) => {
setCardNumber(e.target.value)
}
useEffect(()=>{
if(cardNumber.length===4)
setcardNumber(cardNumber+" ")
else if (cardNumber.length === 9) {
setcardNumber(cardNumber+" ")
} else if (cardNumber.length === 14) {
setcardNumber(cardNumber+" ")
}
}, [cardNumber]);
Sorry for any typos.