In this post, we’ll walk through a simple React.js example of how to build a password meter display to indicate the strength of a password as the user types it. We’ll use React, the useState hook, and react-bootstrap to build this prototype. The pre-requisites for this post are that you have set up the boilerplate for a new react app and installed react-bootstrap.

Overview

Here is a screenshot of the app that we will build in this post. As the user types, the app evaluates the strength of the password until it meets the criteria acceptable by the app for a secure password. This is indicated by the tooltip text underneath the password field and the color-coded password meter which will be the focus of this post.

The Code

The structure of this app is incredibly simple. I have written the whole of the solution in the <App /> component. I did refactor the boilerplate generated by create-react-app so that the App component is in its own subfolder of a components folder. I also trimmed down the index.js in the root of the project to only load the <App /> component and do nothing else.

There are a number of sections of the code that we will discuss here. If you would like to get a copy of the code yourself you can fork it from the following github repo.

The JSX

The following code block renders the JSX to show the password input field and the strength meter below it. I used the bootstrap Form and ProgressBar components to create this look. The styles applied to the page can be found in the App.module.css file and are applied to the JSX wherever you see {styles.SomeClass} in the following code. The key observations to make are the onChange property of <Form.Control> which calls the handleOnChange() function and is how the password is evaluated as it is typed in the field. Secondly, the call to the displayMeter() function below the <Form.Control> is called every time the App is rendered. Because the app is rendered every time the state is updated and the state is updated every time a new character is typed into the input field. This means that the password meter is updated as you type, realtime. This also provides a very nice experience for the user.

 * Displays the JSX
 */
return (
  <div className={styles.App}>

    <header className={styles.Header}>
      <img src={logo} className={styles.Logo} alt="logo" />
    </header>

    <main className={styles.Main}>
      <h1 className={styles.PageHeading}>Password Strength Meter</h1>

      <Form className={styles.Form}>
        <Form.Group controlId="password">
          <Form.Control
            className={styles.Field}
            type="text"
            placeholder="Password"
            onChange={handleOnChange}
          />

          {displayMeter()}

          <Form.Text className="text-muted">
            * Password should be greater than 10 characters and have at least
            one letter and number.
          </Form.Text>
        </Form.Group>
      </Form>

    </main>
  </div>
)

Initializing State

The following shows how to initialize state in a React functional component with useState(). in this case, I’ve initialized it with an object that contains the value and strength of the password. Each of these values is updated as the user types each character into the password field through the handleOnChange() function. It is important to note that the useState hook returns a reference to the value and a function that you use to set the value. When you initialize state you destructure these into their respective constants, in our case password and setPassword.

/**
 *  Initializes state to store the password and it's strength as a
 *  person types in the password input field.
 */
const [password, setPassword] = useState({ value: "", strength: 0 })

Handle on Change

This function is where everything gets started. You’ll notice in the JSX returned by the render() function that the handleOnChange() is bound to the onChange property of the password field. This means that every time someone types a new character into this field the handleOnChange() function is called. There are two key jobs of handleOnChange(). First, the latest value is stored for the password, and second the strength of the password is evaluated using the evaluateStrength() function and the result is also stored. You’ll notice that the password state object is copied into a new constant called newState using a spread operator. This is done because state in react is by reference and not by value and therefore, to prevent funny things from happening it is always best practice to make a copy of the state, update the copy, and then when you’re ready update the state directly. This is what I’ve done in this function.

/**
 * Called each time a new character is typed into the password input field
 * and updates the value and strength state for the password.
 * @param {Event} e
 */
const handleOnChange = e => {
  const newValue = e.target.value
  const newState = { ...password }

  newState.value = newValue
  newState.strength = evaluateStrength(newValue)

  setPassword(newState)
}

Evaluate Strength

The handleOnChange() function calls the evaluateStrength() function with the new value that has been keyed into the password field as a parameter. This function is very simple and could be expanded to include more criteria for determining whether or not a password is weak or strong. In this case there are 2 criteria used. First the length of the password, it should be greater than 10 characters. Second, whether or not the password contains both numbers and letters. If neither of these are true then the strength is rated as 0 which is very weak. If it’s 10 characters long but has only numbers or letters than the rating increases from 0 to 1 but it is still not very strong. It is only when both numbers and letters are found in the password that it meets the criteria in this case for a strong password. Albeit, this is a simple example, I think it does a good job of showing the power of react and useState to provide a robust user experience.

/**
 * Determines the strength of the password on a scale of -1 to 2
 * where -1 is an unsupported criteria and 2 is the strongest.
 * @param {number} aValue
 */
const evaluateStrength = aValue => {
  //is less than 10 characters
  if (aValue.length < 10) {
    return 0
  }

  //has at least 10 characters but is only numbers or letters
  if (/^[a-zA-Z]+$/i.test(aValue) || /^[0-9]+$/i.test(aValue)) {
    return 1
  }

  //is greater than 10 characters and has at least one number and letter
  if (/\d/.test(aValue) && /[a-zA-Z]/.test(aValue)) {
    return 2
  }

  return -1
}

Display Meter & Set Meter

The handleOnChange() and evaluateStrength() functions keep the password state up to date as a user types. But, the next step is to get this state to actually render something useful on the UI so that the user knows when they have keyed in a password that is considered strong. This is where the displayMeter() function comes into play. Remember in the JSX that displayMeter() is called on each render when state changes. This means that the meter is being constantly updated to reflect the latest values stored in state for the value and strength of the password. The logic for rendering the meter based on the ever-changing state has been broken up into two functions. First, displayMeter() which evaluates the strength of the password but does not directly render the corresponding JSX for the ProgressBar. This second task has been delegated to the setMeter() function which takes in the color and size that is to be used to determine how the ProgressBar will display once evaluated. The combination of these two functions results in the realtime rendering of the password strength meter as the user types.

/**
 * Called in the render to determine what kind of meter should be displayed
 * based on the strength rating of the password.
 */
const displayMeter = () => {
  if (password.strength === 0) {
    return setMeter("danger", password.value.length)
  }

  if (password.strength === 1) {
    return setMeter("warning")
  }

  if (password.strength === 2) {
    return setMeter("success")
  }
}
/**
 * Used to return the appropriate meter to display based on the
 * strength rating of the password.
 * @param {string} color
 * @param {number} size
 */
const setMeter = (color, size) => {
  switch (color) {
    case "danger":
      return (
        <ProgressBar className={styles.Meter}>
          <ProgressBar striped variant={color} now={size * 2} key={1} />
        </ProgressBar>
      )
    case "warning":
      return (
        <ProgressBar className={styles.Meter}>
          <ProgressBar striped variant="danger" now={20} key={1} />
          <ProgressBar striped variant={color} now={40} key={2} />
        </ProgressBar>
      )
    case "success":
      return (
        <ProgressBar className={styles.Meter}>
          <ProgressBar striped variant="danger" now={20} key={1} />
          <ProgressBar striped variant="warning" now={40} key={2} />
          <ProgressBar striped variant={color} now={40} key={3} />
        </ProgressBar>
      )
    default:
      break
  }
}