Exercise: Payment Card Numbers

Exercise 12 Average

Prerequisites for the exercise

  1. React Forms — Controlled Components

Objective

Create a CardNumberInput component that renders an input field with auto-formatting for a payment card number.

Description

We all are familiar with payment card numbers, are we? They are basically just sequences of digits that act as the unique identifier of one's payment card.

Payment card numbers range in length (of digits) from 8 to 19 digits. However, the most common length is 16, and for the purposes of this exercise, we'll stick to this length.

Needless to say, today's world is the world of online payments and transactions. We all use payment card numbers quite a lot online — to buy groceries, shop new home furniture, book airline tickets, choose from a host of holiday tour guides, and whatnot.

In this regard, web applications leverage HTML forms to obtain these card numbers.

A naive way to obtain payment card numbers is to have a number input field (with type="number"), where we could only enter digits. However, there is a problem with allowing users to enter card numbers this way, without any formatting.

Card number input without any formatting
Card number input without any formatting

That is, there is no visual demarcator between the blocks of digits and thus it becomes quite difficult to enter card numbers correctly and cross-check them against the numbers printed on the card.

If you notice, most payment card issuing companies print the card numbers in blocks of 4 digits. This is done in order to make it easy enough for people to read the number and tally it against its entry elsewhere when using the card.

So coming back to the input field, it's recommended to NOT use such a kind of input field meant for payment card numbers — it's just TOO BAD for UX. We should instead use a field that auto-formats the data as we enter it into the field.

For instance, following is an example of such an input field:

Card number input with formatting
Card number input with formatting

As we enter digits into the field, we automatically get a space added after every 4-digit block. This is done by some clever JavaScript listening data input events on the input field.

In this exercise, you have to create a CardNumberInput component that represents an input field meant for obtaining a 16-digit payment card number, with some auto-formatting applied to it.

While there are possibly many different aspects of this auto-formatting, we'll keep things very simple:

  • Truncate any non-digit characters (including spaces) from the input.
  • Make sure that the total number of digits does not exceed 16 digits.
  • Add a space after every 4-digit block.

This auto-formatting should be done as we enter data into the input field.

Besides this, the component can accept any number of props which get relayed forward to the underlying <input> element rendered by the component.

Also note that the <input> element MUST be of type "text"; it MUST NOT be of type="number". (In fact, even with type "number", we'd have to look for non-digit characters being entered into the input field anyways, and so we're better off at using a generic text field from the get go.)

Shown below is a demonstrative usage of a <CardNumberInput>:

function App() {
   return (
      <CardNumberInput/>
   );
}

Here's how this code should work in your program:

Live Example

In the link above, try entering digits and non-digits, and typing as many as 16 digits. Notice how the input field auto-formats everything you enter.

View Solution

New file

Inside the directory you created for this course on React, create a new folder called Exercise-12-Payment-Card-Numbers and put the .js solution files for this exercise within it.

Solution

Let's start by creating a controlled <input> component inside CardNumberInput. Further discussion is deferred until we complete this:

function CardNumberInput(props) {
   const [value, setValue] = useState('');
   return (
      <input
         type="text"
         value={value}
         onChange={e => setValue(e.target.value)}
         {...props}
      />
   );
}

Great. Let's now think about implementing the auto-formatting logic.

The idea is that as soon as 'dirty' data is input into the field, React would fire the onChange event handler, which we could configure to sanitize this input value. Once sanitized, we can use the resulting value to update the value state. In this way, only a desirable, formatted view of the input would be presented inside the field, regardless of what we enter into it.

Perfect. With this basic plan in hand, let's go on and implement it.

First things first, let's replace the call to setValue() above with a custom function that encapsulates the entire input value sanitization logic. We'll call this function updateValue().

Following, we make this change in the code:

function CardNumberInput(props) {
   const [value, setValue] = useState('');

function updateValue(value) {
var sanitizedValue = value;
// Sanitization happens here...
setValue(sanitizedValue);
} return ( <input type="text" value={value}
onChange={e => updateValue(e.target.value)} {...props} /> ); }

Over to the updateValue() function.

Inside updateValue() above, we save the provided value argument inside another local variable, sanitizedValue. We're supposed to perform all the sanitization logic on this variable and then, in the end, pass it to the setValue() state updater function in order to update the component's value state.

So the only thing we need to worry about now is the sanitization logic.

Hmm. Where do we begin?

Well, it's pretty simple at least in how the exercise simplifies everything for us.

A fully-fledged card number input would be an overkill to create, requiring a whole bunch of different things and JavaScript APIs.

Here's how we can cleanse the given value stored in sanitizedValue:

  • To remove all non-digit characters, we can use the string replace() method with a regular expression to match every single non-digit character. The replacement string would then become ''.
  • To limit the total number of digits to 16, we can simply slice the value (obtained in the previous step) from the first character up to and including the 16th character.
  • To add a space after every 4-digit block, we can again use the string replace() method with a regular expression to match a sequence of four digits. The replacement string would become '$1 ', where $1 denotes a regex backreference holding the matched 4-digit block in it.

Let's implement all three of these things in updateValue():

function CardNumberInput(props) {
   const [value, setValue] = useState('');

   function updateValue(value) {
      var sanitizedValue = value
         .replace(/[^\d]/g, '')
         .slice(0, 16)
         .replace(/(\d{4})/g, '$1 ')
         .trim();
      setValue(sanitizedValue);
   }

   return (
      <input
         type="text"
         value={value}
         onChange={e => updateValue(e.target.value)}
         {...props}
      />
   );
}

Notice the trim() call in the code above. It makes sure that we don't have a space at the end of the input data (after the previous replace() call adds a space after a 4-digit block).

And this is it. Let's now try the component:

function App() {
   return (
      <CardNumberInput/>
   );
}

Live Example

Voila! It works perfectly.

And this completes this exercise.