Shalitha Suranga Programmer | Author of Neutralino.js | Technical Writer

Implementing react-input-mask for web apps

8 min read 2498

Implementing react-input-mask for web apps

Modern web applications accept various user inputs: mouse clicks, screen tap and keyboard events, gestures, audio, and files.

Almost all web applications use keyboard events to capture detailed user input data. In other words, users can enter required data into typical web applications using their devices’ keyboards. For example, a user can enter a name, phone number, postal code, and email address during a particular web application’s registration process, and that information is then communicated to the backend.

Every user is different. They may have unique practices when it comes to form-filling, and they often try to use various formats in input fields. If a particular web application displays a raw text input field to capture a phone number, for example, the user may face several usability issues, such as not being able to predict the input field’s accepted format when they’re typing.

To avoid these types of issues, you can use input masks to provide user-friendly constraints for a particular user input field. In this article, I’ll explain how you can use input masking in your React applications.

What is input masking?

In user experience (UX) design, an input mask refers to a string template that is created based on a specific input format to prevent transcription errors. If the user tries to enter an invalid character, the masked input element blocks the invalid keystroke. For example, users won’t be able to enter English letters in a masked phone number field.

Frontend developers can use input masks to help ensure clean and valid user inputs are sent to their web application backends. At the same time, input masks motivate users to enter clean, correct inputs.

Input masking in React applications

There are several libraries that we can use to create input masks in React. The react-input-mask library is the most popular one, and it offers a very flexible API for developers. As a bonus, its behavior follows a UX-friendly design pattern.

You can either test this tutorial’s code snippets in a new React project or use them in your existing project. If you are going to get started with a new one, create a new project with the following command:

npx create-react-app input-masking

Next, install the react-input-mask npm package to create input masks in your React application.

npm install react-input-mask --save

Now, let’s try to create a simple masked input box for a phone number to check whether or not the library works. Add the following code into your App.js file or component.

We made a custom demo for .
No really. Click here to check it out.

import { useState } from 'react';
import InputMask from 'react-input-mask';
function PhoneInput(props) {
  return (
    <InputMask 
      mask='(+1) 999 999 9999' 
      value={props.value} 
      onChange={props.onChange}>
    </InputMask>
  );
}
function App() {
  const [phone, setPhone] = useState('');
  const handleInput = ({ target: { value } }) => setPhone(value);
  return (
    <div>
      <PhoneInput 
        value={phone} 
        onChange={handleInput}>
      </PhoneInput>
      <div style={{paddingTop: '12px'}}>Phone: {phone}</div>
    </div>
  );
}
export default App;

If you start the React development server with npm start or yarn start, you will see a masked input to enter a standard USA phone number (starting with the +1 dialing code), as shown below:

A simple example for masked input in React
A simple example for masked input in React.

We implemented a reusable component to accept phone numbers with the library’s InputMask component. The library’s generic mask component responds to the following React props:

  • mask: a template of the user input with predefined special characters — 9 for matching a number, a for a letter, and * for either a number or a letter
  • maskChar: this character can be used to fill the missing part of the input string; the default value is _ (underscore)
  • formatChars: you can change the default characters and regular expressions used for masking (in the mask property) by changing this property
  • alwaysShowMask: the library usually shows the mask when the user focuses on the masked input; set this property to true if you always want to show the mask

Like any other typical input element, we can use the onChange event to change the component state based on user inputs.

Basic react-input-mask examples

The input masking concept can be used in many different scenarios to enhance the user-friendliness of web applications. Check the following basic masking examples.

4-digit PIN

function PINInput(props) {
  return (
    <InputMask 
      mask='9999' 
      value={props.value} 
      onChange={props.onChange}
      placeholder='Enter PIN'>
    </InputMask>
  );
}

Google-style OTP

function GoogleOTP(props) {
  return (
    <InputMask 
      mask='G-999999' 
      maskChar={null}
      value={props.value} 
      onChange={props.onChange}>
    </InputMask>
  );
}

Standard credit card

function CreditCardInput(props) {
  return (
    <InputMask 
      mask='9999 9999 9999 9999' 
      value={props.value} 
      onChange={props.onChange}>
    </InputMask>
  );
}

Simple date input

Format: DD-MM-YYYY

function DateInput(props) {
  return (
    <InputMask
      mask='99-99-9999'
      placeholder='DD-MM-YYYY'
      value={props.value}
      onChange={props.onChange}>
    </InputMask>
  );
}

Format: YYYY-MM-DD

function DateInput(props) {
  return (
    <InputMask
      mask='9999-99-99'
      placeholder='YYYY-MM-DD'
      value={props.value}
      onChange={props.onChange}>
    </InputMask>
  );
}

The above masked input offers a simple date input with minimal constraints. It accepts only digits for the year, month, and day, but it accepts invalid user inputs like 2020–50–50. Most masked date inputs out there are implemented like this due to backend validations and the user’s knowledge about standard calendars.

However, I will explain how to block invalid dates from date input masks in the upcoming advanced masking examples section.

Advanced react-input-mask examples

The above examples use built-in regular expressions for masking. For example, the four-digit PIN’s mask uses 9 to match digits.

However, we cannot implement every masking rule with these predefined regular expressions. In other words, we need to define custom masking characters with different regular expressions. In some scenarios, we also need to have dynamic masks, where the user’s input directs the constraints of subsequent characters they enter in the field.

Below, we’ll look at some advanced examples that use custom masking characters and conditional masks.

A custom product code

Assume that you need to implement a mask to capture a valid product code in a grocery store management app written in React. Let’s define the rules for a valid product code as such:

  • Every product code starts with P (single product) or K (kit)
  • The rest of the product code consists of four even digits

We need to define custom masking characters to implement the above rules. Check the following code.

const MaskedInput = (props) => {
  // Defining custom masking characters
  // P will match P or K
  // 0 (zero) will match even digits
  const formatChars = {
    'P': '[PK]',
    '0': '[02468]'
  };
  return (<InputMask 
    mask='P0000' 
    value={props.value} 
    onChange={props.onChange}
    formatChars={formatChars}
    placeholder='Eg: P2266'>
  </InputMask>)
};

Time input

Assume that you need to ask the user to enter a time by using the hh:mm format. The simpler implementation of the time input mask may use the 99:99 mask, but users can still enter some invalid time entries, such as 59:10, 08:65, etc.

An input mask typically refers to a string input with a specific format — not related to strict validation. However, we can apply conditional masking based on user input to add improvements to these scenarios.

For example, we can only accept the digits 0, 1, and 2 for the first mask character position. After that, we can change the second mask character’s logic based on the first digit of the user input.

Take a look at the following code that uses conditional masking.

function TimeInput(props) {
  let mask = '12:34';
  let formatChars = {
    '1': '[0-2]',
    '2': '[0-9]',
    '3': '[0-5]',
    '4': '[0-9]'
  };

  let beforeMaskedValueChange = (newState, oldState, userInput) => {
    let { value } = newState;

    // Conditional mask for the 2nd digit base on the first digit
    if(value.startsWith('2'))
      formatChars['2'] = '[0-3]'; // To block 24, 25, etc.
    else
      formatChars['2'] = '[0-9]'; // To allow 05, 12, etc.
    return {value, selection: newState.selection};
  }
return (
    <InputMask 
      mask={mask}
      value={props.value} 
      onChange={props.onChange}
      formatChars={formatChars}
      beforeMaskedValueChange={beforeMaskedValueChange}>
    </InputMask>
  );
}

The beforeMaskedValueChange event will be triggered before the masked input field gets updated with the most recent user input. This means we can use it to change the mask dynamically.

Improved date input

In the previous simple date input examples, we used the default format character 9 for the mask. Those implementations provide correct input masks for users to enter a date, but they don’t provide strict validation. Therefore, we can implement validation for the date mask like in the previous time input.

Format YYYY-MM-DD input

We are going to improve the date input mask by adding the following validation rules:

  • The month part accepts only 0 and 1 for the first digit. If the user entered 0, the second digit position allows any digit between 1 and 9. If the user entered 1, the user can only enter digits between 0 and 2 for the second digit’s location
  • The date part accepts digits based on the last day of the user-entered month. For example, if the user entered 2022-02, the masked input accepts only integers from 01 to 28 for the date part, since 2022 February’s last day is 28

We can implement the above validation rules with react-input-mask library API, as shown below.

import { useState } from 'react';
import InputMask from 'react-input-mask';

function TimeInput(props) {
  let mask = 'YYYY-mM-dD';
  let formatChars = {
    'Y': '[0-9]',
    'm': '[0-1]',
    'M': '[0-9]',
    'd': '[0-3]',
    'D': '[1-9]'
  };

  let beforeMaskedValueChange = (newState, oldState, userInput) => {
    let { value } = newState;

    let dateParts = value.split('-');
    let yearPart = dateParts[0];
    let monthPart = dateParts[1];
    let dayPart = dateParts[2]

    // Conditional mask for the 2nd digit of month based on the first digit
    if(monthPart.startsWith('1'))
      formatChars['M'] = '[0-2]'; // To block 13, 15, etc.
    else
      formatChars['M'] = '[1-9]'; // To allow 05, 08, etc - but blocking 00.

    // Conditional mask for day
    if(!yearPart.includes('_') && !monthPart.includes('_')) {

      // Find last day of the month
      let endOfMonth = new Date(`${yearPart}-01-01`);
      endOfMonth.setMonth(parseInt(monthPart));
      endOfMonth.setDate(0);
      let lastDayOfMonth = endOfMonth.getDate().toString();

      // Set [0-x] dynamically for the first digit based of last day
      formatChars['d'] = `[0-${lastDayOfMonth[0]}]`;

      if(dayPart.startsWith(lastDayOfMonth[0]))
        formatChars['D'] = `[0-${lastDayOfMonth[1]}]`; // Limit month's last digit based on last day
      else if(dayPart.startsWith('0'))
        formatChars['D'] = '[1-9]'; // To block 00.
      else
        formatChars['D'] = '[0-9]'; // To allow days to start with 1 Eg: 10, 12, 15, etc.
    }

    return {value, selection: newState.selection};
  }
  return (
    <InputMask
      mask={mask}
      value={props.value}
      onChange={props.onChange}
      formatChars={formatChars}
      beforeMaskedValueChange={beforeMaskedValueChange}>
    </InputMask>
  );
}

function App() {
  const [date, setDate] = useState('');
  const handleInput = ({ target: { value } }) => setDate(value);
  return (
    <div>
      <TimeInput
        value={date}
        onChange={handleInput}>
      </TimeInput>
      <div style={{paddingTop: '12px'}}>Date: {date}</div>
    </div>
  );
}
export default App;

Format DD-MM-YYYY input

For the DD-MM-YYYY date format, we have to enter the date before the month. Therefore, we cannot implement a proper validation for date digits.

As a solution, we can simplify the validation logic by allowing an integer value up to 31 for the date part regardless of the month — assuming that the user knows the end date of a specific month. Look at the following DD-MM-YYYY formatted date mask validation.

import { useState } from 'react';
import InputMask from 'react-input-mask';

function TimeInput(props) {
  let mask = 'dD-mM-YYYY';
  let formatChars = {
    'Y': '[0-9]',
    'd': '[0-3]',
    'D': '[0-9]',
    'm': '[0-1]',
    'M': '[1-9]'
  };

  let beforeMaskedValueChange = (newState, oldState, userInput) => {
    let { value } = newState;

    let dateParts = value.split('-');
    let dayPart = dateParts[0];
    let monthPart = dateParts[1];

    // Conditional mask for the 2nd digit of day based on the first digit
    if(dayPart.startsWith('3'))
      formatChars['D'] = '[0-1]'; // To block 39, 32, etc.
    else if(dayPart.startsWith('0'))
      formatChars['D'] = '[1-9]'; // To block 00.
    else
      formatChars['D'] = '[0-9]'; // To allow 05, 15, 25  etc.


    // Conditional mask for the 2nd digit of month based on the first digit
    if(monthPart.startsWith('1'))
      formatChars['M'] = '[0-2]'; // To block 15, 16, etc.
    else
      formatChars['M'] = '[1-9]'; // To allow 05, 06  etc - but blocking 00.

    return {value, selection: newState.selection};
  }
  return (
   <InputMask
      mask={mask}
      value={props.value}
      onChange={props.onChange}
      formatChars={formatChars}
      beforeMaskedValueChange={beforeMaskedValueChange}>
    </InputMask>
  );
}

function App() {
  const [date, setDate] = useState('');
  const handleInput = ({ target: { value } }) => setDate(value);
  return (
    <div>
      <TimeInput
        value={date}
        onChange={handleInput}>
      </TimeInput>
      <div style={{paddingTop: '12px'}}>Date: {date}</div>
    </div>
  );
}
export default App;

How to style input masks

The react-input-mask library renders masked input boxes using typical HTML inputs. Therefore, you can change styling by adding CSS classes as you wish. If you would like to use another component instead of HTML input, such as Material, implement your component as below.

import InputMask from 'react-input-mask';
import Input from '@material-ui/core/Input';

const MaskedInput = (props) => (
  <InputMask 
    mask='9999' 
    maskChar={null} 
    value={props.value} 
    onChange={props.onChange}
    placeholder='Enter your PIN'>
    {(inputProps) => <Input {...inputProps} />}
  </InputMask>
);

The above code snippet renders the Material input element instead of the native HTML input element as the masked PIN input field.

React input masking with Material UI components

react-input-mask alternatives

The react-input-mask library is a flexible library for creating any masked input. Unfortunately, it’s no longer actively maintained, so developers who use this library started looking for alternatives. As a result, now there is an active fork, and it is well-maintained.

Also, there are several alternative libraries that you can use instead of react-input-mask. If you are implementing masks for only digits, such as credit card numbers, dates, monetary values, the react-number-format library is a good alternative. For example, you can implement an input mask for a 4-digit PIN with the following code snippet.

import NumberFormat from 'react-number-format';

<NumberFormat format="####" mask="_" placeholder="Enter PIN"/>

The react-maskedinput library offers most features that react-input-mask has, such as dynamic masking, copy-pasting support, predefined format characters, etc. Also, it offers a feature to transform user inputs. For example, you can implement a simple license plate number mask that only accepts capital letters and digits, as shown below.

import MaskedInput from 'react-maskedinput';

<MaskedInput mask="AAA 1111" placeholder="ABC 1234"/> // A: Auto uppercase letter

However, the react-input-mask library offers a more comfortable way to handle complex masks with custom validations compared to react-maskedinput. Therefore, react-input-mask is suitable for complex and dynamic input masks.

Conclusion

The input masking concept is suitable for capturing user inputs with a specific, standard format, such as IP addresses, credit card numbers, product codes, and ID card numbers, among others.

The maintainer of the react-input-mask library is planning to release a new stable version (v3.0), and already there is a v3.0 alpha version with some enhancements and modifications to the API. The next stable release may contain their current development branch’s implementation. We used the current stable version (v2.0.4) in this tutorial because alpha-versioned dependencies are not recommended for any production system.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Shalitha Suranga Programmer | Author of Neutralino.js | Technical Writer

One Reply to “Implementing react-input-mask for web apps”

Leave a Reply