Virtual Keypad

Hide a page behind a virtual keypad lock.


Environment

Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.

Specification

Write a functional component that accepts two props: a 4-digit combination and a React component. Your component should display a keypad that allows the user to attempt to enter the 4-digit combination and shows the combination as they enter it above the keypad. If the combination is entered correctly, the site should display the component that is passed to it as a prop (and remove the keypad). The component in its initial state is pictured below:

The finished product
The finished product

After the users clicks four numbers, if the combination entered does not match the combination passed to the component in the first prop, issue an alert as pictured below and reset the keypad.

The alert issued when a user enters the wrong combination
The alert issued when a user enters the wrong combination

Starter Code

Here is some code to get you started:

import React, { useState } from "react";

function App() {
  const unlockedScreen = () => (
    <div style={{ textAlign: "center" }}>You are logged in</div>
  );

  return (
    <CombinationLock combination={[1, 2, 3, 4]} NextScreen={unlockedScreen} />
  );
}

const CombinationLock = ({ combination, NextScreen }) => {
  // YOUR CODE HERE
};

export default App;

Initial values for the two props are included as well. Feel free to divide the challenge into multiple subcomponents.

Solution

import React, { useState } from "react";

function App() {
  const unlockedScreen = () => (
    <div style={{ textAlign: "center" }}>You are logged in</div>
  );

  return (
    <CombinationLock combination={[1, 2, 3, 4]} NextScreen={unlockedScreen} />
  );
}

const CombinationLock = ({ combination, NextScreen }) => {
  const [entered, setEntered] = useState([]);
  const [loggedIn, setLoggedIn] = useState(false);

  const combinationsMatch = (combo) => {
    console.assert(combo.length == 4);

    for (let i = 0; i < combination.length; i++) {
      if (combination[i] != combo[i]) return false;
    }
    return true;
  };

  const numberPress = (number) => {
    const newCombo = [...entered, number];
    setEntered(newCombo);
    if (newCombo.length == 4) {
      if (combinationsMatch(newCombo)) setLoggedIn(true);
      else {
        setEntered([]);
        alert("Incorrect combination");
      }
    }
  };

  const DigitRow = ({ startVal }) => {
    return (
      <div style={{ display: "flex" }}>
        {[startVal, startVal + 1, startVal + 2].map((num) => (
          <div
            style={{ height: 150, width: 150, borderStyle: "solid" }}
            onClick={() => numberPress(num)}
          >
            {num}
          </div>
        ))}
      </div>
    );
  };

  return loggedIn ? (
    <NextScreen />
  ) : (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        textAlign: "center",
        marginTop: 100,
        fontSize: 100,
      }}
    >
      <div style={{ height: 150, width: 450, borderStyle: "solid" }}>
        {entered.join("")}
      </div>
      <DigitRow startVal={1} />
      <DigitRow startVal={4} />
      <DigitRow startVal={7} />
      <div
        style={{
          textAlign: "center",
          height: 150,
          width: 150,
          borderStyle: "solid",
        }}
        onClick={() => numberPress(0)}
      >
        0
      </div>
    </div>
  );
};

export default App;

We first establish two state variables: an array to store the user’s combination as they enter it and a boolean that is set to true after the user enters the correct combination. The component conditionally renders the keypad or the component passed via props depending on the boolean's value. We define two helper functions in the component. combinationsMatch(combo) determines if the argument passed matches the correct combination, and numberPress(number) enables us to create onClick functions for each of the keypad’s digit with repeating code. It accepts a digit, appends that digit to the currently entered combination, and handles submission behavior if the digit entered is the fourth in the combination. I also separate the HTML for a row of three digits into a separate functional component called DigitRow defined within the CombinationLock component for better readability. The formatting of the digits (as a column containing five rows: a row for the current combination, three rows with three digits each, and a row just containing 0) is done with flexbox.


useState