React - How To Format Phone Number As User Types
Solution 1:
You can normalize
the input
like so
- the
value
is up-to-date in relation toevent.target.value
previousValue
is what has already been validated and set tostate
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 ifno value
if (!value) return value;
// only allows 0-9 inputs
const currentValue = value.replace(/[^\d]/g, '');
const cvLength = currentValue.length;
if (!previousValue || value.length > previousValue.length) {
// returns: "x", "xx", "xxx"if (cvLength < 4) return currentValue;
// returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
if (cvLength < 7) return`(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;
// returns: "(xxx) xxx-", (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)}`;
}
};
constnormalizeInput = (value, previousValue) => {
if (!value) return value;
const currentValue = value.replace(/[^\d]/g, '');
const cvLength = currentValue.length;
if (!previousValue || value.length > previousValue.length) {
if (cvLength < 4) return currentValue;
if (cvLength < 7) return`(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;
return`(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`;
}
};
constvalidateInput = value => {
let error = ""if (!value) error = "Required!"elseif (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
return error;
};
classFormextendsReact.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 } }) {
this.setState(prevState=> ({ phone: normalizeInput(value, prevState.phone) }));
};
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(
<formclassName="form"onSubmit={this.handleSubmit}><divclassName="input-container"><pclassName="label">Phone:</p><inputclassName="input"type="text"name="phone"placeholder="(xxx) xxx-xxxx"value={this.state.phone}onChange={this.handleChange}
/>
{this.state.error && <pclassName="error">{this.state.error}</p>}
</div><divclassName="btn-container"><buttonclassName="btn danger"type="button"onClick={this.handleReset}
>
Reset
</button><buttonclassName="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: 010px;
cursor: pointer;
text-align: center;
box-sizing: border-box;
padding: 030px;
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: 010px;
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;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script><divid='root'></div>
Or... have 3 separate inputs and combine them when done.
constvalidateInput = value => {
let error = ""if (!value) error = "Required!"elseif (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
return error;
};
const initialState = {
areaCode: "",
prefix: "",
suffix: "",
error: ""
};
classFormextendsReact.Component {
constructor() {
super();
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleReset = this.handleReset.bind(this);
this.setInputRef = this.setInputRef.bind(this);
}
handleChange({ target: { name, value } }) {
let valueChanged = false;
this.setState(prevState => {
const nextValue = value.replace(/[^\d]/g, '');
const changedValue = prevState[name];
if (changedValue.length !== nextValue.length) valueChanged = true;
return { [name]: nextValue }
}, () => {
if(valueChanged) this.handleFocus(name)
});
};
setInputRef(name, element) {
this[name] = element;
}
handleFocus(name){
const { areaCode, prefix, suffix } = this.state;
const areaCodeFilled = areaCode.length === 3;
const prefixFilled = prefix.length === 3;
if(areaCodeFilled && name === "areaCode") {
this.prefix.focus();
this.prefix.selectionEnd = 0;
} elseif(prefixFilled && name === "prefix") {
this.suffix.focus();
this.suffix.selectionEnd = 0;
}
}
handleSubmit(e) {
e.preventDefault();
const { areaCode, prefix, suffix } = this.state;
const phoneNumber = `(${areaCode}) ${prefix}-${suffix}`const error = validateInput(phoneNumber);
this.setState({ error }, () => {
if(!error) {
setTimeout(() => {
alert(phoneNumber);
}, 300)
}
});
}
handleReset() {
this.setState(initialState);
};
render() {
return(
<formclassName="form"onSubmit={this.handleSubmit}><divclassName="input-container"><divclassName="label">
Phone:
</div><divclassName="parenthesis"style={{marginLeft:10, marginRight:2}}>(</div><inputclassName="input area-code"type="text"name="areaCode"placeholder="xxx"value={this.state.areaCode}onChange={this.handleChange}maxLength="3"
/><divclassName="parenthesis"style={{marginRight:2}}>)</div><inputref={node => this.setInputRef("prefix", node)}
className="input prefix"
type="text"
name="prefix"
placeholder="xxx"
value={this.state.prefix}
onChange={this.handleChange}
maxLength="3"
/>
<divclassName="dash">-</div><inputref={node => this.setInputRef("suffix", node)}
className="input suffix"
type="text"
name="suffix"
placeholder="xxxx"
value={this.state.suffix}
onChange={this.handleChange}
maxLength="4"
/>
</div><pclassName="error">{this.state.error}</p><divclassName="btn-container"><buttonclassName="btn danger"type="button"onClick={this.handleReset}
>
Reset
</button><buttonclassName="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: 010px;
cursor: pointer;
text-align: center;
box-sizing: border-box;
padding: 030px;
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;
height: 24px;
margin-top: -20px;
padding-left: 26%;
color: red;
text-align: right;
}
.input {
display: flex;
height: 40px;
font-size: 16px;
width: 33%;
padding: 03px;
background: #fff;
color: #666;
outline: none;
border: 0;
}
.area-code,.prefix {
width: 27px;
}
.suffix {
width: 38px;
}
.dash,.parenthesis {
display: flex;
}
.input-container {
width: 100%;
margin-bottom: 20px;
display: flex;
flex-direction: row;
align-items: center;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border: 1px solid #e5e5e5;
transition: .2s ease-in-out;
transition-property: color,background-color,borde
}
.label {
height: 100%;
background: rgb(238, 238, 238);
width: 25%;
padding-top: 8px;
display: flex;
text-transform: uppercase;
justify-content: space-around;
font-weight: bold;
height: 34px;
}
.primary {
background-color: #1e87f0;
}
.primary:hover {
background-color: #0f7ae5;
color: #fff;
}
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script><divid='root'></div>
Solution 2:
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.
Solution 3:
I have used this library: https://www.npmjs.com/package/libphonenumber-js it has a AsYouType function, that you can use on your inputs
Solution 4:
perhaps, something as the following will work for someone:
onChange={(e) => {
if (e.target.value.length < 13) {
var cleaned = ("" + e.target.value).replace(/\D/g, "");
let normValue = `${cleaned.substring(0, 3)}${
cleaned.length > 3 ? "-" : ""
}${cleaned.substring(3, 6)}${
cleaned.length > 6 ? "-" : ""
}${cleaned.substring(6, 11)}`;
handleChange(normValue);
}
}}
Post a Comment for "React - How To Format Phone Number As User Types"