OTP Login System in ReactJS: A Front-End Machine Coding Interview Question

OTP Login System in ReactJS: A Front-End Machine Coding Interview Question

In today’s digital world, verifying user identity is crucial for secure applications, and one of the most common ways to do so is through One-Time Passwords (OTP). This blog will walk you through a React component for implementing a phone number login feature with OTP verification. You’ll learn how to handle form validations, manage state effectively, and create a smooth user experience for OTP entry.

What this Blog Will Teach You:

  1. Phone Number Validation:
    • Length Check: The phone number must be exactly 10 digits long. If the phone number is shorter or longer, the system will prompt the user to correct it.
    • Input Masking: As the user types, non-numeric characters will be removed automatically, allowing only valid digits to be entered.
  2. OTP Input Validations:
    • Numeric Only: The OTP input only allows numeric characters. Any non-numeric characters will be automatically filtered out, ensuring only valid digits are entered.
    • Length Validation: The OTP input is restricted to exactly 4 digits, matching the standard OTP format. The user will be alerted if the OTP length is incorrect.
    • Focus Management: As the user enters a digit in one OTP field, the focus will automatically shift to the next field to facilitate smoother input.
    • Backspace Handling: If the user needs to correct the OTP, pressing the “Backspace” key will move the focus to the previous field if it is empty, allowing for easy corrections.
  3. User Interaction Improvements:
    • Real-Time Feedback: The system will provide real-time validation as the user types the phone number, alerting them of any errors, such as incorrect number of digits.

Code Walkthrough

1. App.jsx – The Entry Point

import "./App.css";
import PhoneOtpForm from "./components/phone-login";

function App() {
  return (
    <div className="App">
      <h1>Login with Phone</h1>
      {/* Render the Phone OTP Form Component */}
      <PhoneOtpForm />
    </div>
  );
}

export default App;

2. App.css – Basic Styling

.App {
  font-family: sans-serif;
  text-align: center;
}

.otpInput {
  width: 40px;
  height: 40px;
  margin: 5px;
  text-align: center;
  font-size: 1.2em;
}

3. PhoneLogin.jsx – Phone Input and OTP Handling

import { useState } from "react";
import OtpInput from "./OtpInput";

const PhoneOtpForm = () => {
  // State to store the entered phone number
  const [phoneNumber, setPhoneNumber] = useState("");
  // State to toggle OTP input visibility
  const [showOtpInput, setShowOtpInput] = useState(false);

  // Handles phone number input and ensures only numeric values
  const handlePhoneNumber = (event) => {
    setPhoneNumber(event.target.value.replace(/[^0-9]/g, ""));
  };

  // Validates phone number length and simulates sending OTP
  const handlePhoneSubmit = (event) => {
    event.preventDefault();
    if (phoneNumber.length !== 10) {
      alert("Please enter a valid 10-digit phone number.");
      return;
    }
    console.log("OTP Sent to: ", phoneNumber); // Mock API call
    setShowOtpInput(true);
  };

  // Handles OTP submission
  const onOtpSubmit = (otp) => {
    console.log("Login Successful with OTP: ", otp); // Log OTP for debugging
  };

  return (
    <div>
      {!showOtpInput ? (
        <form onSubmit={handlePhoneSubmit}>
          <input
            type="tel"
            value={phoneNumber}
            onChange={handlePhoneNumber}
            placeholder="Enter Phone Number"
            maxLength={10}
          />
          <button type="submit">Submit</button>
        </form>
      ) : (
        <div>
          <p>Enter OTP sent to {phoneNumber}</p>
          <OtpInput length={4} onOtpSubmit={onOtpSubmit} />
        </div>
      )}
    </div>
  );
};

export default PhoneOtpForm;

Explanation:

  1. State Management:
    • phoneNumber: Stores the phone number entered by the user.
    • showOtpInput: Controls whether the OTP input fields are displayed. Initially set to false, indicating that the OTP fields are not shown until the phone number is validated.
  2. Handling Phone Number Input:
    • The handlePhoneNumber function ensures that only numeric values are accepted in the phone number input field. The regular expression /[^0-9]/g replaces any non-numeric characters with an empty string.
  3. Phone Number Validation and OTP Sending:
    • When the user submits the phone number, the handlePhoneSubmit function checks if the phone number is exactly 10 digits long. If not, an alert is shown asking for a valid 10-digit number.
    • If the number is valid, a mock API call simulates sending the OTP to the phone number, and the OTP input fields are shown by setting showOtpInput to true.
  4. OTP Submission:
    • When the OTP is successfully entered, the onOtpSubmit function is triggered. In the current code, it simply logs the OTP to the console for debugging.
  5. Conditional Rendering:
    • If the showOtpInput state is false (phone number not validated), a phone number input form is displayed.
    • If showOtpInput is true, the OTP input fields are shown using the OtpInput component.

4. OtpInput.jsx- OTP Input Handling

import { useRef, useState, useEffect } from "react";

const OtpInput = ({ length = 4, onOtpSubmit = () => {} }) => {
  // State to store each OTP digit
  const [otp, setOtp] = useState(new Array(length).fill(""));
  // References for input fields to handle focus
  const inputRefs = useRef([]);

  // Focus the first input box when the component loads
  useEffect(() => {
    inputRefs.current[0]?.focus(); // Focus first input field on load
  }, []);

  // Handles changes in each OTP input field
  const handleChange = (index, e) => {
    const value = e.target.value.replace(/[^0-9]/g, ""); // Allow only numbers
    const newOtp = [...otp];
    newOtp[index] = value; // Update value at specified index
    setOtp(newOtp);

    // Combine OTP values and submit when complete
    const combinedOtp = newOtp.join("");
    if (combinedOtp.length === length) {
      console.log("Submitted OTP: ", combinedOtp); // Debugging OTP value
      onOtpSubmit(combinedOtp);
    }

    // Move focus to the next input field
    if (value && index < length - 1) {
      inputRefs.current[index + 1]?.focus();
    }
  };

  // Handles "Backspace" key press to move focus to the previous input field
  const handleKeyDown = (index, e) => {
    if (
      e.key === "Backspace" &&
      !otp[index] &&
      index > 0 &&
      inputRefs.current[index - 1]
    ) {
      // Move focus to the previous input field on backspace
      inputRefs.current[index - 1].focus();
    }
  };

  // Handles the submit button click
  const handleSubmit = () => {
    const combinedOtp = otp.join("");
    if (combinedOtp.length === length) {
      onOtpSubmit(combinedOtp); // Submit OTP if valid
    } else {
      alert("Please enter the complete OTP.");
    }
  };

  return (
    <div>
      {otp.map((value, index) => (
        <input
          key={index}
          ref={(el) => (inputRefs.current[index] = el)}
          value={value}
          onChange={(e) => handleChange(index, e)}
          onKeyDown={(e) => handleKeyDown(index, e)} // Add the keydown event listener
          maxLength={1}
          className="otpInput"
        />
      ))}
      <button onClick={handleSubmit}>Submit OTP</button> {/* Submit Button */}
    </div>
  );
};

export default OtpInput;

Explanation of the handleChange Function:

  • handleChange is triggered when the user types in an OTP input field.
    Numeric Input Only:
    • It restricts the input to numbers by removing any non-numeric characters using e.target.value.replace(/[^0-9]/g, "").
    Updating OTP State:
    • The function updates the corresponding position in the otp array with the new value entered by the user.
    Moving Focus Forward:
    • If a number is entered and it’s not the last field (index < length - 1), the focus automatically shifts to the next input field, making it easier for the user to fill in the OTP without extra clicks.
    Submitting the OTP:
    • When all the input fields are filled (combinedOtp.length === length), the complete OTP is combined into a string and passed to the onOtpSubmit function for further processing (like OTP verification).

Explanation of the handleKeyDown Function:

  • handleKeyDown is triggered when a key is pressed inside an OTP input field.
    Backspace Key:
    • If the user presses “Backspace” and the current input field is empty (!otp[index]), the focus will move to the previous input field (if it exists) by checking index > 0.
    • This allows the user to easily move backward when they want to delete characters from the previous fields..

Explanation of the handleSubmit Function:

  • handleChange is triggered when the user types in an OTP input field.
    Combining OTP:
      • It combines all the input field values into a single string (combinedOtp).
    Checking OTP Completion:
    • If the combined OTP string length matches the required length (combinedOtp.length === length), it calls the onOtpSubmit function to process the OTP.
    • If the OTP is incomplete, it alerts the user to enter the complete OTP, ensuring that all fields are filled before submission.

Conclusion

This setup shows how to build an OTP-based login system in ReactJS that’s easy to manage and grow. It uses React features like hooks for managing state, and conditional rendering to create a user-friendly interface that adjusts as needed.

The system is designed to handle OTP verification smoothly. It includes features like real-time validation, automatic focus shifting between input fields, and ensuring only numeric input is accepted. This helps make the process of entering OTPs quick and hassle-free.

For real-world use, you can connect this system with services like Twilio or Firebase Authentication. These services send OTPs via SMS, adding a layer of security and convenience for users, making the whole login process smoother and more reliable.

Leave a Comment