From jQuery to React

Building a better credit card form at expressPay

Introduction

I believe that paying for stuff online should be easy. That’s why, in my honest opinion, any bit of UI the user has to interact with on their way to that sweet confirmation page, has to either, be helping them get there faster or shouldn’t be there at all.

At expressPay, our service allows anyone to instantly send money to any bank account or mobile money number, buy airtime and pay for online services using, among other methods, their debit card. Our debit card form hadn’t changed in a long time. It got the job done, but, the way I saw it, could benefit from a makeover that focused on improving clarity, simplicity and user happiness.

My job was to design the credit card form, given a set of business requirements and constraints. This post is about the design considerations our team explored to arrive at the finished product.

Preparation

I knew from the get go, that I wanted to write the new form in React. Why? First off, I should mention that our current form was written with HTML, CSS and jQuery. Not only did this make the code sort of wonky, but IMHO, it made it incredibly painful to look at, and more importantly, to reason about. Secondly, I love how React made you think of your apps as you build them. React’s component driven paradigm also made it incredibly easy to write reusable components, that ultimately reduce the amount of code you write and allow you to build apps more efficiently.

Starting at the basics

At it’s most basic, a credit card form is essentially just a collection of inputs — emphasis here on inputs — that allowed a user to enter the different bits of information that make up their card details.

So my first order of business was to craft a simple, but versatile input component, that, with minimal effort, I could reuse to collect the different bits of information that made up the user’s card details.

Building the input component

First came the question of styling — Although we wanted this component to be as generic as possible, there were still business requirements concerning how the input should look. This was after all an expressPay text input component, and our style guide had clearly prescribed the baseline look for text inputs used on our site. Here’s a visual of what that looks like:

So, the takeaway here is that the input was going to come bootstrapped with with default styling that conformed to our style guide, but would still allow the consumer override these defaults if they wanted to.

Also, because we wanted to be able to take advantage of the full css spec when styling our components, we skipped inline styles and decided to go with Aphrodite.

Next came the question of state — specifically how much, if any, internal state should the component was was allowed to manage. Here, we decided that although we wanted to pass in as much as we could via props we also wanted the component to at least keep track of whether or not it had the focus, internally. This meant adding a hasFocus field to its internal state.

Now it came identifying the different constituent parts that together, make up the text input component.

HTMLTextInput provides the actual input into which the user types InputIndicator an optional component, passed in via props, that if present will be rendered to the far right end of the bordered box. In the case of card number field, this InputIndicator could be used to show the card issuer’s logo. BorderedBox is a div with a solid border that contains HTMLTextInput and InputIndicator This is what the user perceives as the actual input, since it HTMLTextInput doesn’t render with a border. ErrorMessage renders an error message beneath the bordered box, if an error is present. InputContainer : is a div that contains all the above mentioned items

With all the constituent parts laid out, I moved forward with the implementation. Let’s take a crack at it.

Here’s the TextInput Component.

The Generic Text Input Component

Building more specialised input components

With our generic TextInput component in place, we could now shift our attention to creating the more specialised components that our credit card form needed. In no particular order, these are CardCVVInput , CardExpiryInput , CardNumberInput and CardHolderNameInput respectively.

Here’s the CardNumberInput component:

Here’s the CardHolderNameInput component:

Here’s the CardExpiryInput component:

Here’s the CardCVVInput component:

Oh — you’ll notice that components depend on functions and constants imported from ../utils . Here’s the contents of that file:

Next on the agenda was, writing the CreditCardForm component itself. This component would bring everything together by doing the following:

Visually combining the various input components we previously put together in such a way as to achieve our overarching objective — allowing users to enter their card information. Holding all state necessary to capture the user’s card information, as well as the functions responsible for managing that state. Passing down state (and the functions that manage that state) to the various input components it encloses. Housing validation logic and handling errors

A word on validations and error handling.

When it came to validations we knew we wanted to simplify as much as possible. Our objective was to prevent as much bad input as we could from being entered in the first place.

As a result, you’ll see that some of our more specialised input components include an onKeyDown function, containing logic to reject bad inputs, which they pass down to the underlying TextInput component. We felt that this approach reduced the amount of time we spent yelling at users via error messages while allowing us get them to success more quickly.

Secondly, we chose to trigger validation logic on blur because, by leaving an input, the user was more or less indicating that they were done providing that piece of information. We also chose to not lock the user into an erroneous field by preventing them from leaving it. The user is free to move to the next input, and come back later to fix any errors shown.

Without further ado, here’s the code for the CreditCardForm component.

And here’s what it looks like when rendered:

The full code, along with a working demo can be found here. Feel free to take it for a spin. If you find a bug, or have feedback or a criticism, it is all very welcome. I’d love to hear from you.

Final Words…

I had a lot of fun putting this together. The payments space is not exactly sexy, but learning and understanding the user interactions behind accepting a credit card payment was really fun.