Insights

Creating a Secure Password Indicator Within a Form using React and Formik

The Final Product

Getting Started

Boxes and forms are well… what form the internet.

If you’re a front-end or full-stack developer, at some point you may be required to create a registration form to let users sign up for a website.

If you are feeling experimental, we can even make the registration form a bit more spicey than your traditional “Enter email”, “Enter password” and “Confirm password”.

One thing I do want to note before we get started is that although you aren’t limited to implementing this form with just React, the article will only cover how to achieve so in React. I’ll also be skipping out on diving into the CSS for the form as it’s fairly simple, but feel free to dig into the code used if you want to learn more about it.

Right, I’ll spare the basic overview of what a signup form is and dive straight into the topic of this article, where we will be creating a secure password registration form.

What Our Form Will Do

The form will do the following -

  • Allow a user to enter an email address for their account
  • Allow a user to enter a password for their new account
  • We will test the password against a set of rules that we’ll specify
  • Display the user's password progress on meeting all rules on form interactions
  • Provide descriptive errors if the user submits the form before completing the requirements

Sounds like a complex set of features for a front-end developer, even with React. Having a form handle validation, updating state and providing errors all based on the user's input.

Formik

This is where one of my favourite packages, Formik comes into play.

Formik in a nutshell -

Formik takes care of the repetitive and annoying stuff--keeping track of values/errors/visited fields, orchestrating validation, and handling submission--so you don't have to. This means you spend less time wiring up state and change handlers and more time focusing on your business logic.

(Formik 2019)

So without further ado, let us get the base markup into an editor for us to show an interface to our user.

I’ll go through each part of the code step by step so don’t be put off by the large block of code.

Our App

import React from "react";
import ReactDOM from "react-dom";
import "normalize.css";
import "./styles.css";

import FancyRegistration from "./FancyRegistration";
import StyledWrapper from "./Styles";

function App() {
  return (
    <div className="App">
      <StyledWrapper>
        <FancyRegistration />
      </StyledWrapper>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Fancy Registration Component

Complete Snippet

import React, { useState } from "react";
import { Formik } from "formik";

import Requirements from "./Requirements";

const FancyRegistration = props => {
  const [long, longEnough] = useState(false);
  const [number, hasNumber] = useState(false);

  return (
    <Formik
      initialValues={{ email: "", password: "" }}
      validate={values => {
        values.password.length < 8 ? longEnough(false) : longEnough(true);
        !/\d/.test(values.password) ? hasNumber(false) : hasNumber(true);
      }}
      onSubmit={(values, { resetForm, setErrors, setSubmitting }) => {
        let errors = {};

        if (!long || !number)
          errors.password =
            "Your password doesn't currently meet the requirements";

        // If the errors object is empty then we've successfully met all the requirements
        if (
          Object.entries(errors).length === 0 &&
          errors.constructor === Object
        ) {
          alert(`Great, we've created an account for ${values.email}`);
          resetForm(); // Reset form for the example
        } else {
          setErrors(errors);
        }

        setSubmitting(false);
      }}
    >
      {({ errors, values, handleChange, handleSubmit, isSubmitting }) => (
        <>
          <h1>Create Your Account</h1>
          <form onSubmit={handleSubmit}>
            <label htmlFor="email">What's your email address?</label>
            <input
              autoFocus
              id="email"
              type="email"
              name="email"
              onChange={handleChange}
              required
              value={values.email}
            />
            <label htmlFor="password">
              What do you want your password to be?
            </label>
            <input
              id="password"
              type="password"
              name="password"
              onChange={handleChange}
              required
              value={values.password}
            />
            {errors.password && (
              <label className="error" htmlFor="password">
                Sorry! {errors.password}
              </label>
            )}
            <Requirements long={long} number={number} />
            <button
              type="submit"
              value="Create account"
              disabled={isSubmitting}
            >
              Create account
            </button>
          </form>
        </>
      )}
    </Formik>
  );
};

export default FancyRegistration;

Component Imports

Our user registration form kicks off with importing React and the ‘useState’ hook.

We then import Formik so that we can wrap our form in the event handlers and validation events.

Our final import is of a component we will return to later which is a custom ‘Requirements’ component. This will be used to feedback to our users when they’ve met the password requirements we will set.

import React, { useState } from "react";
import { Formik } from "formik";

import Requirements from "./Requirements";

Cool, imports are done.

Setting Component State

Next up is setting some state variables.

The ‘useState’ hook allows us to set conditional state variables which will indicate to our user when they’ve met our password requirement.

const FancyRegistration = props => {
  const [long, longEnough] = useState(false);
  const [number, hasNumber] = useState(false);

Our ‘long’ state variable will be either true or false (boolean) and will be switched between the two if the user enters a password eight characters in length or more.

Similar to the ‘long’ variable, the ‘number’ variable will be toggled between true or false, the state of this variable will change when the user has at least one number in their password or not.

Building the Registration Form

Finally, we will build our registration form.

 return (
    <Formik
      initialValues={{ email: "", password: "" }}
      validate={values => {
        values.password.length < 8 ? longEnough(false) : longEnough(true);
        !/\d/.test(values.password) ? hasNumber(false) : hasNumber(true);
      }}
      onSubmit={(values, { resetForm, setErrors, setSubmitting }) => {
        let errors = {};

        if (!long || !number)
          errors.password =
            "Your password doesn't currently meet the requirements";

        // If the errors objcet is empty then we've successfully met all the requirements
        if (
          Object.entries(errors).length === 0 &&
          errors.constructor === Object
        ) {
          alert(`Great, we've created an account for ${values.email}`);
          resetForm(); // Reset form for the example
        } else {
          setErrors(errors);
        }

        setSubmitting(false);
      }}
    >
      {({ errors, values, handleChange, handleSubmit, isSubmitting }) => (
        <>
          <h1>Create Your Account</h1>
          <form onSubmit={handleSubmit}>
            <label htmlFor="email">What's your email address?</label>
            <input
              autoFocus
              id="email"
              type="email"
              name="email"
              onChange={handleChange}
              required
              value={values.email}
            />
            <label htmlFor="password">
              What do you want your password to be?
            </label>
            <input
              id="password"
              type="password"
              name="password"
              onChange={handleChange}
              required
              value={values.password}
            />
            {errors.password && (
              <label className="error" htmlFor="password">
                Sorry! {errors.password}
              </label>
            )}
            <Requirements long={long} number={number} />
            <button
              type="submit"
              value="Create account"
              disabled={isSubmitting}
            >
              Create account
            </button>
          </form>
        </>
      )}
    </Formik>
  );

Now that's a lot of code, so we'll be breaking this down into smaller steps following on from an overview of what we've done.

As mentioned, we are using Formik for our event handling, so we will wrap all of our HTML markup in the Formik component and use the Formik props to trigger events based on user interaction.

To start off the Formik component, we set our initial values which will be empty strings.

Validating The Form

Next, we compose a validation event. This validation event by default gets called when the form triggers an onBlur event, onInput event or onSubmit event. This validation event is where the meat of the article really is.

In this validation event, we have access to the values of the form and is where we will do our checks to see if the password value meets the custom requirements we’re asking of the user.

Now before we go into the event itself, I first want to describe the two requirements we are going to ask of the user when setting a password. These two requirements include -

  1. The password must be at least 8 characters in length
  2. The password must contain at least 1 numeric character

Back to the form!

validate={values => {
   values.password.length < 8 ? longEnough(false) : longEnough(true);
   !/\d/.test(values.password) ? hasNumber(false) : hasNumber(true);
}}

To kick off the event, we are passing in the values object to our function, this contains the values of any form fields we’ve included (email and password).

Now comes the actual validation.

Using a ternary operator, we are asking the form to return a boolean based on whether the length count of the form field ‘password’ is less than 8 characters long.

You can think of a ternary operator as a single line if/else statement.

If the statement returns true then we know the user hasn’t met the requirement and we set the state variable to false.

If the statement returns false, it means the user has entered a password at least 8 characters long and so we set the state variable to true, indicating the user has met our requirement.

We then rinse and repeat this syntax, swapping out the statement to test whether or not the password contains a numeric character. Again, we toggle a state variable depending on the boolean value returned by our test.

Processing Submissions

Next up we create the logic for processing the form submission.

onSubmit={(values, { resetForm, setErrors, setSubmitting }) => {
   let errors = {};
   
   if (!long || !number)
      errors.password = "Your password doesn't currently meet the requirements";

   // If the errors objcet is empty then we've successfully met all the requirements
   if (
      Object.entries(errors).length === 0 &&
      errors.constructor === Object
   ) {
      alert(`Great, we've created an account for ${values.email}`);
      resetForm(); // Reset form for the example
   } else {
      setErrors(errors);
   }
   
   setSubmitting(false);
}}

We start this by creating an empty object for any errors we want to display to the user within our markup later on. If this object has no properties then we won’t show any errors to our users.

After creating the errors object, we then check the true/false (boolean) values of our state variables. Where ‘long’ represents if the password string is 8 characters long and ‘number’ represents if the password has a numeric value.

If either of these values is false, then the password hasn’t met our requirements and we attach a message related to the password error we want to display to our users. The message displayed is “Your password doesn't currently meet the requirements”.

To wrap up our submit event, we check if the errors object has any values.

If the errors object is empty then we can successfully process the user's data. To simulate this we are throwing up an alert box but in production, you’d replace this with the actual processing of a users account creation.

If the errors object isn’t empty, we use the Formik function setErrors to… you guessed it, set the errors state for the form!

Finally, we set the submitting state of the form to false.

Phew.

That’s all of the processing of the data for the form and its state.

Building Form Fields

Now we move onto building the fields.

I won’t go into too much detail of the basic markup, but I will make a note of the helpful functions Formik gives us for certain elements.

<>
   <h1>Create Your Account</h1>
   <form onSubmit={handleSubmit}>
      <label htmlFor="email">What's your email address?</label>
      <input
         autoFocus
         id="email"
         type="email"
         name="email"
         onChange={handleChange}
         required
         value={values.email}
      />
      <label htmlFor="password">
         What do you want your password to be?
      </label>
      <input
         id="password"
         type="password"
         name="password"
         onChange={handleChange}
         required
         value={values.password}
      />
      {errors.password && (
         <label className="error" htmlFor="password">
            Sorry! {errors.password}
      )}
      <Requirements long={long} number={number} />
      <button
         type="submit"
         value="Create account"
         disabled={isSubmitting}
      >
         Create account
      </button>
   </form>
</>

As we have two neighboured children elements (H1 and form) we have to wrap the elements in a React.Fragment, the shorthand syntax being an empty element (<></>).

After our wrapper, and the page title, is the form element.

Formik gives us a handleSubmit function which we pass directly into the forms onSubmit property.

Next thing to note is the input fields.

Normally in React, you are responsible for creating state variables that you’d use for the input values, but again Formik is kind enough to provide an object which contains the values of the initial values we set all the way back at the start of the Formik construction process.

To make use of them, the input field value is set to the object key value of the field you want access to. So our email field will use value={values.email} and password will use value={values.password}.

Pretty cool, right? This is why I love Formik.

To listen out for any changes to the fields and process their new values, we attach a handleChange event that Formik provides to our inputs onChange properties.

We’ve now got the form with inputs that update the form state, next up is displaying our errors to the user if available.

In our code, we are conditionally checking to see if there are any errors for the field password. If there are then we are rendering a label with the error message. The reason for this being a label is so that when a user clicks on it, it’ll focus on the input it is referencing.

Finally, we add a submit button to the form so that users can get to our onSubmit event. It’s a standard button with the type of submit thrown on it.

The only thing to note here is that we are adding a dynamic disabled state. It’s always good practice to prevent a user from submitting a form if it’s already in the process of submitting. Otherwise, we could create duplicate accounts or throw unexpected errors. To make this state dynamic we make use of the Formik variables isSubmitting.

So that’s everything to do with building the form itself, including our validation and submission logic.

Requirements Component

Now onto the fun part, I mean the extra fun part of this guide ?

We’ll be moving onto our requirements component.

import React from "react";
import Requirement from "./Requirement";

const Requirements = ({ long, number }) => (
  <section className="strength-meter">
    <h2>Password Requirements</h2>
    <Requirement
      htmlFor="password"
      isvalid={long}
      invalidMessage="We like long passwords, at least 8 characters if you could"
      validMessage="Sweet, that's long enough for us!"
    />
    <Requirement
      htmlFor="password"
      isvalid={number}
      invalidMessage="Make it tricky to guess, adding a number makes it more secure"
      validMessage="Nice, now you're safer than the average Joe"
    />
  </section>
);

export default Requirements;

Our requirements component takes in two props, which are our state boolean values, long and number.

In the requirements component, we are also importing and using another component, the requirement component (singular). This nested component takes in 4 props.

  1. The input field name as a HTML for attribute
  2. isValid which will accept a boolean value for toggle if the requirement has been met or not
  3. invalidMessage which is shown when our isValid value is false
  4. validMessage which is shown when our isValid value is true

As we have two fields, I’ve included two of these components with references to our long and number state value props that were passed down by the Formik component.

Requirement Component

Our final component is the Requirement component.

import React from "react";
import { FaCheck } from "react-icons/fa";

const Requirement = ({ htmlFor, isvalid, validMessage, invalidMessage }) => (
  <label htmlFor={htmlFor} className={!isvalid ? `invalid` : `valid`}>
    <FaCheck />
    <span>{!isvalid ? invalidMessage : validMessage}</span>
  </label>
);

export default Requirement;

The requirement component is made up of a label which references an input field (password in our case). The CSS classes applied to this element will be dynamic based on the isValue value. The classes are responsible for changing the visual validity state to the end-user.

The component content includes an icon which will change colour depending on if the requirement has been met and a text message which alternates again based on if the requirement has been met.

Conclusion

So we’ve built a pretty rad user registration form.

This is only an example of what can be achieved with Formik, some basic validation on the front-end and the use of error message handling, validation visual feedback and more.

Using the logic implemented in this article, you could extend the form to include more fields, or ask of stricter requirements.

Maybe try adding in a requirement that asks the user to include at least one capital letter in their password?

I invite you to build your own example and share it with the world, whether it is for learning, fun or in production and tweet at me with your forms! @whatjackhasmade

Continue Reading 📚

Tell Me About Your Project