How to normalize an input (Colloquially known as how to create an input mask)
Steve Clark 🤷♀️
Posted on September 14, 2020
Table of Contents
Motivation
Being part of the Vets Who Code organization, part of our training is helping to maintain the website. We train vets in the whole process, from HTML, CSS, JavaScript, JAMStack, ReactJS, GatsbyJS, JestJS testing, and a mess of other technologies. One of the "tickets" for the website was to format the phone field in a contact form so when the user types in their number it will automatically, as they type, format the phone from 1111111111 to 111-111-1111. This tutorial is what I took away from it.
An input mask is a way of formatting data to a standard form. For instance, in the US a zip code is five numbers. When a user types in a form on a website and it POSTs to your database you want the info preformatted for your use, plus you want to make it easier for the user to type in the information.
You can help people enter data correctly into by providing input masks for fields that contain data that is always formatted a certain way. For example, you can use an input mask to make sure that people enter correctly formatted phone numbers into a phone number field.
In this article I am going to show you some tricks to create an input mask in React.
Prerequisites
A basic understanding of HTML, CSS, and JavaScript are needed for this tutorial. Also your favorite code editor (I'm using VS Code) I will do my best to show everything else.
Setup
Start with creating a new React App. In your command line type:
npx create-react-app input-mask-tutorial
cd input-mask-tutorial
npm start or yarn start
Delete the boilerplate that comes pre-loaded with a React App like everything between the <div className="App">
and the App.css
file. Download a new one here. It'll make it easier because we won't have to spend time styling the form.
Go ahead and create a form with three inputs, a button, and somewhere to display our 'output':
import React from "react";
import "./App.css";
function App() {
return (
<div className="App">
<h1>Input Mask Example</h1>
<h2>Form Example</h2>
<form className="testform">
<input type="text" placeholder="First name" name="firstName" />
<input type="text" placeholder="Last name" name="lastName" />
<input type="tel" placeholder="Telephone" name="phone" />
<input type="reset" value="Reset" />
</form>
<h2>Form Submitted Data</h2>
<pre>
<code>{JSON.stringify(form)}</code>
</pre>
</div>
);
}
export default App;
Project
Now we can add some action to these input fields.
- Import
useState
. - Add an
initialState
. - Add a form state. This allows us to update the state with new values that are typed in the form.
- Add a way to reset the form.
- Add a way to "set" the values in the form.
import React, { useState } from "react";
import "./App.css";
function App() {
const initialState = {
firstName: "",
lastName: "",
phone: "",
};
const [form, setForm] = useState(initialState);
const reset = (event) => {
event.preventDefault();
setForm({ ...form, ...initialState });
};
return (
<div className="App">
<h1>Input Mask Example</h1>
<h2>Form Example</h2>
<form onReset={reset} className="testform">
<input
type="text"
placeholder="First name"
name="firstName"
value={form.firstName}
onChange={(event) => {
const { value } = event.target;
setForm({ ...form, firstName: value });
}}
/>
<input
type="text"
placeholder="Last name"
name="lastName"
value={form.lastName}
onChange={(event) => {
const { value } = event.target;
setForm({ ...form, lastName: value });
}}
/>
<input
type="tel"
placeholder="Telephone"
name="phone"
value={form.phone}
onChange={(event) => {
const { value } = event.target;
setForm({ ...form, phone: value });
}}
/>
<input type="reset" value="Reset" />
</form>
</div>
);
}
export default App;
With the addition of the App.css
that you copied it should look something like this:
As it stands right now, our form can accept input values but there is no client-side input validation nor are there input masks to format the fields like we want to. We basically have a form we can type whatever we want into it and reset the form.
Upper Case Mask
For the first name let's go ahead and make the entry all upper case. This is a common input mask and one of the easier ones to accomplish. We are just going to upper case as the user type.
<input
type="text"
placeholder="First name"
name="firstName"
value={form.firstName}
onChange={(event) => {
const { value } = event.target;
setForm({
...form,
firstName: value.replace(/[^A-Za-z]/gi, "").toUpperCase(),
});
}}
/>
Let's see how this works. First we destructure the value
from event.target.value
. Then we set the state of the form by adding the first name to it. The value.replace()
will take what value, as we type, and perform an input validation on it. The String.prototype.replace()
method does exactly like it sounds: it will replace whatever you want with something else. For example, we can easily replace every occurrence of the word "Navy" with "Rubber Ducky Patrol". In this case we are using a regular expression to check for the occurrence of anything that is not an English letter. The toUpperCase()
will then take whatever is a letter and uppercase it. Easy.
Upper case first letter
For the last name we are going to only uppercase the first letter. Sort of a formal way of writing someones name. So steve will turn into Steve.
<input
type="text"
placeholder="Last name"
name="lastName"
value={form.lastName}
onChange={(event) => {
const { value } = event.target;
setForm({
...form,
lastName:
value
.replace(/[^A-Za-z]/gi, "")
.charAt(0)
.toUpperCase() + value.slice(1),
});
}}
/>
Again we have an onChange
event in the input
. We destructure the value
from event.target.value
. Then we set the last name using the String.prototype.replace()
method, scrubbing the string for anything that is not a letter. Then we use String.prototype.charAt()
method to find the first letter (zero indexed) uppercase it, then add the rest of the string to the end.
Phone number input mask
Our last input mask is a little tricky. We want an input that looks like this: (XXX) XXX-XXXX as we type. If a user types 3-digits it should look like this (123)
. When they type 7-digits it will look something like this (123) 456
. Then when they type in all 10-digits it will look something like this (123) 456-7890
. We can easily use the String.prototype.replace()
method but it won't do it as we type. 🤔
Let's go ahead and move the logic out of the App
component and take a real close look what we need to do.
We can mask the input based on the previous value, adding digits and required punctuation as we type by comparing string lengths.
Here's a list of things we should do:
- First we check if there is even a value. Else it will return
undefined
. - Next we only allow digits 1-9. We do this by using the
String.prototype.replace()
method. - Check that the length of input value is greater than the previous value. Since the previous value is going to be the initial value of phone number, and is an empty string, that's what we are comparing to.
- Now the magic. If the length of the value we are typing equals 3 then we add a parentheses to the each side
(123)
. We accomplish by using aTemplate literal
. Template literals are string literals allowing embedded expressions. - If the length is 6 then do this
(123) 456
- Finally return the whole completed string with the parentheses and the hyphen.
Here's the function described above:
const normalizePhone = (value, previousValue) => {
// Any value at all?
if (!value) return value;
// replace method to only allow digits 1-9
const nums = value.replace(/[^\d]/g, ""); // only allows 0-9
// If the length of value is greater than nothing
if (!previousValue || value.length > previousValue.length) {
// Is the length = 3? If true, add a parentheses to each side (123)
if (nums.length === 3) return `(${nums})`;
// Is the length = 6? If true, add a parentheses to each side (123)
// and add the other three numbers
if (nums.length === 6) return `(${nums.slice(0, 3)}) ${nums.slice(3)}`;
// These next two statements cover everything in between all numbers being equal
if (nums.length <= 3) return nums;
if (nums.length <= 6) return `(${nums.slice(0, 3)}) ${nums.slice(3)}-`;
// Finally add add a parentheses to each side (123)
// Add the next three numbers
// Add a hyphen and the last 4 numbers
return `(${nums.slice(0, 3)}) ${nums.slice(3, 6)}-${nums.slice(6, 10)}`;
}
};
Let's put it to work in the input field.
<input
type="tel"
placeholder="Telephone"
name="phone"
value={form.phone}
onChange={(event) => {
const { value } = event.target;
const phoneMask = normalizePhone(value, initialFormState.phone);
setForm({
...form,
phone: phoneMask,
});
}}
/>
It took a little more work but check it out:
Easy, right?
Wrapping it up
In this lesson we covered a couple ways to create an input mask for an input, as a user types. It is just reusing the String.prototype.replace()
method, a few regular expressions, and some clever logic. Input masks are great for good UX/UI design and are helpful for when you want to POST to your database. I hope you enjoyed the tutorial. Leave a comment down below.
Here's all the code:
import React, { useState } from "react";
import "./App.css";
const normalizePhone = (value, previousValue) => {
if (!value) return value;
const nums = value.replace(/[^\d]/g, ""); // only allows 0-9
if (!previousValue || value.length > previousValue.length) {
if (nums.length === 3) return `(${nums})`;
if (nums.length === 6) return `(${nums.slice(0, 3)}) ${nums.slice(3)}`;
if (nums.length <= 3) return nums;
if (nums.length <= 6) return `(${nums.slice(0, 3)}) ${nums.slice(3)}`;
return `(${nums.slice(0, 3)}) ${nums.slice(3, 6)}-${nums.slice(6, 10)}`;
}
};
function App() {
const initialFormState = {
firstName: "",
lastName: "",
phone: "",
};
const [form, setForm] = useState(initialFormState);
const reset = (event) => {
event.preventDefault();
setForm({ ...form, ...initialFormState });
};
return (
<div className="App">
<h1>Input Mask Example</h1>
<h2>Form Example</h2>
<form onReset={reset} className="testform">
<input
type="text"
placeholder="First name"
name="firstName"
value={form.firstName}
onChange={(event) => {
const { value } = event.target;
setForm({
...form,
firstName: value.replace(/[^A-Za-z]/gi, "").toUpperCase(),
});
}}
/>
<input
type="text"
placeholder="Last name"
name="lastName"
value={form.lastName}
onChange={(event) => {
const { value } = event.target;
setForm({
...form,
lastName:
value
.replace(/[^A-Za-z]/gi, "")
.charAt(0)
.toUpperCase() + value.slice(1),
});
}}
/>
<input
type="tel"
placeholder="Telephone"
name="phone"
value={form.phone}
onChange={(event) => {
const { value } = event.target;
setForm({
...form,
phone: normalizePhone(value, initialFormState.phone),
});
}}
/>
<input type="reset" value="Reset" />
</form>
<h2>Form Submitted Data</h2>
<pre>
<code>{JSON.stringify(form)}</code>
</pre>
</div>
);
}
export default App;
CodeSandBox
Live
Vets Who Code
Did you like what you read? Want to see more?
Let me know what you think about this tutorial in the comments below.
As always, a donation to Vets Who Code goes to helping veteran, like myself, in learning front end development and other coding skills. You can donate here: VetsWhoCode
Thanks for your time!
Posted on September 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 14, 2020