I need to format a 10 digit string to this format: '(123) 456-7890'.
However, I need this to happen as the user types. So if the user has inputted only 3 digits, the input should display: '(123)'.
If they've inputted 5 digits the input should display: '(123) 45'
With my current code, the formatting only takes place after the 10th character is entered. I'd like it so that it formats it from the third character onwards.
const phoneRegex = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/
const handleInput = (value) => {
return (
value.replace(phoneRegex, '($1) $2-$3')
)
}
class FindASubscriber extends React.Component {
constructor(props) {
super(props)
this.state = {
value: ''
}
}
render() {
const { label, placeholder, feedback } = this.props
const { value} = this.state
return (
<div className="find__a__subscriber">
<FlexGrid>
<FlexGrid.Col>
<FlexGrid.Row>
<Input
feedback={feedback}
label={label}
type="text"
pattern="[0-9]*"
placeholder={placeholder}
value={handleInput(value)}
maxLength="10"
onChange={
(event) => this.setState({value: event.target.value})
}
/>
</FlexGrid.Row>
</FlexGrid.Col>
</FlexGrid>
</div>
)
}
}```
Your current code's regex only matches when ten digits are entered (3, 3, then 4). You could update the regex to accept a range of digits, such as:
^\(?([0-9]{0,3})\)?[-. ]?([0-9]{0,3})[-. ]?([0-9]{0,4})$
Or you could have the regex simply make sure that 0-10 digits are entered ([0-9]{0,10}) and then split the string yourself into substrings of length 3, 3, and 4. Doing it the latter way seems better since you only want to show certain characters depending on how many digits the user has entered:
1 -> (1
123 -> (123)
1234567 -> (123) 456-7
1234567890 -> (123) 456-7890
You would have to handle each of these cases which a simple replace won't do.
I have used this library: https://www.npmjs.com/package/libphonenumber-js it has a AsYouType function, that you can use on your inputs
It's all about formatting. Any key that prints a character
should cause a rewrite of the input field.
This way the user only sees valid formatted field, no matter what he does.
The regex is simple ^\D*(\d{0,3})\D*(\d{0,3})\D*(\d{0,4})
function getFormattedPhoneNum( input ) {
let output = "(";
input.replace( /^\D*(\d{0,3})\D*(\d{0,3})\D*(\d{0,4})/, function( match, g1, g2, g3 )
{
if ( g1.length ) {
output += g1;
if ( g1.length == 3 ) {
output += ")";
if ( g2.length ) {
output += " " + g2;
if ( g2.length == 3 ) {
output += " - ";
if ( g3.length ) {
output += g3;
}
}
}
}
}
}
);
return output;
}
console.log( getFormattedPhoneNum("") );
console.log( getFormattedPhoneNum("2") );
console.log( getFormattedPhoneNum("asdf20as3d") );
console.log( getFormattedPhoneNum("203") );
console.log( getFormattedPhoneNum("203-44") );
console.log( getFormattedPhoneNum("444sg52asdf22fd44gs") );
console.log( getFormattedPhoneNum("444sg526sdf22fd44gs") );
console.log( getFormattedPhoneNum("444sg526sdf2244gs") );
console.log( getFormattedPhoneNum(" ra098 848 73653k-atui ") );
You could even get fancier and show underscores where a character
should be at any given time.
Like
(___) ___ - ____
(20_) ___ - ____
(123) 456 - ____
etc... (let me know if you want this)
You can normalize
the input
like so
- the
value
is up-to-date in relation to event.target.value
previousValue
is what has already been validated and set to state
This is structured in a way to prevent invalid characters from updating the input and also limits the input to 10 numbers.
Click the Run code snippet
button below for a working example.
const normalizeInput = (value, previousValue) => {
// return nothing if no value
if (!value) return value;
// only allows 0-9 inputs
const currentValue = value.replace(/[^\d]/g, '');
if (!previousValue || value.length > previousValue.length) {
// returns: "x", "xx", "xxx"
if (currentValue.length <= 3) return currentValue;
// returns: "(xxx)"
if (currentValue.length === 3) return `(${currentValue})`;
// returns: "(xxx) x", "(xxx) xx", "(xxx) xxx",
if (currentValue.length <= 6) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;
// returns: "(xxx) xxx-"
if (currentValue.length === 6) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}-`
// returns: "(xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xxxx"
return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`;
}
};
const normalizeInput = (value, previousValue) => {
if (!value) return value;
const currentValue = value.replace(/[^\d]/g, '');
if (!previousValue || value.length > previousValue.length) {
if (currentValue.length <= 3) return currentValue;
if (currentValue.length === 3) return `(${currentValue})`;
if (currentValue.length <= 6) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;
if (currentValue.length === 6) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}-`
return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`;
}
};
const validateInput = value => {
let error = ""
if (!value) error = "Required!"
else if (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
return error;
};
class Form extends React.Component {
constructor() {
super();
this.state = { phone: "", error: "" };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleReset = this.handleReset.bind(this);
}
handleChange({ target: { value } }) {
const normalized = normalizeInput(value, this.state.phone);
this.setState({ phone: normalized });
};
handleSubmit(e) {
e.preventDefault();
const error = validateInput(this.state.phone);
this.setState({ error }, () => {
if(!error) {
setTimeout(() => {
alert(JSON.stringify(this.state, null, 4));
}, 300)
}
});
}
handleReset() {
this.setState({ phone: "", error: "" });
};
render() {
return(
<form className="form" onSubmit={this.handleSubmit}>
<div className="input-container">
<p className="label">Phone:</p>
<input
className="input"
type="text"
name="phone"
placeholder="(xxx) xxx-xxxx"
value={this.state.phone}
onChange={this.handleChange}
/>
{this.state.error && <p className="error">{this.state.error}</p>}
</div>
<div className="btn-container">
<button
className="btn danger"
type="button"
onClick={this.handleReset}
>
Reset
</button>
<button className="btn primary" type="submit">Submit</button>
</div>
</form>
);
}
}
ReactDOM.render(
<Form />,
document.getElementById('root')
);
html {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 16px;
font-weight: 400;
line-height: 1.5;
-webkit-text-size-adjust: 100%;
background: #fff;
color: #666;
}
.btn {
color: #fff;
border: 1px solid transparent;
margin: 0 10px;
cursor: pointer;
text-align: center;
box-sizing: border-box;
padding: 0 30px;
vertical-align: middle;
font-size: .875rem;
line-height: 38px;
text-align: center;
text-decoration: none;
text-transform: uppercase;
transition: .1s ease-in-out;
transition-property: color,background-color,border-color;
}
.btn:focus {
outline: 0;
}
.btn-container {
text-align: center;
margin-top: 10px;
}
.form {
width: 550px;
margin: 0 auto;
}
.danger {
background-color: #f0506e;
color: #fff;
border: 1px solid transparent;
}
.danger:hover {
background-color: #ee395b;
color: #fff;
}
.error {
margin: 0;
margin-top: -20px;
padding-left: 26%;
color: red;
text-align: left;
}
.input {
display: inline-block;
height: 40px;
font-size: 16px;
width: 70%;
padding: 0 10px;
background: #fff;
color: #666;
border: 1px solid #e5e5e5;
transition: .2s ease-in-out;
transition-property: color,background-color,border;
}
.input-container {
width: 100%;
height: 60px;
margin-bottom: 20px;
display: inline-block;
}
.label {
width: 25%;
padding-top: 8px;
display: inline-block;
text-align: center;
text-transform: uppercase;
font-weight: bold;
height: 34px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
background: rgb(238, 238, 238);
}
.primary {
background-color: #1e87f0;
}
.primary:hover {
background-color: #0f7ae5;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>