Menu

Developing the OTP Verification Component

Developing the OTP-Verification Component

The OTP Verification Component is built as a React functional component using useState, useRef, and useEffect hooks to manage the full OTP flow.

When the user clicks the Send OTP button, the fakeSendOTP function generates a random 4-digit OTP using Math.random(), stores it in the generatedOTP state, activates the OTP process, and displays it in a browser alert.

The OTP input fields are rendered using a 4-digit state array, where each digit is controlled by handleChange, with autofocus handled via useRef, backspace navigation via handleKeyDown, and paste support via handlePaste.

Once all four digits are entered, useEffect automatically triggers OTP verification, simulates an API call with a loading state using setTimeout, and compares the entered OTP with the generated one to display a success or error message.

At the same time, a 30-second countdown timer controls the resend OTP button, determining when it becomes active again and maintaining a proper verification flow.

File Path: OTP_Component\src\components\OTP_Component.jsx

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


export default function OTPVerification() {
    const [otp, setOtp] = useState(["", "", "", ""]);
    const [message, setMessage] = useState("");
    const [timer, setTimer] = useState(0);
    const [loading, setLoading] = useState(false);
    const [generatedOTP, setGeneratedOTP] = useState("");
    const [otpSent, setOtpSent] = useState(false);
    const [showResendTimer, setShowResendTimer] = useState(false);
    const [isVerified, setIsVerified] = useState(false);


    const inputsRef = useRef([]);
    const timerRef = useRef(null);


    // Send OTP
    const fakeSendOTP = () => {
        const newOTP = Math.floor(1000 + Math.random() * 9000).toString();


        setGeneratedOTP(newOTP);
        setOtpSent(true);
        setOtp(["", "", "", ""]);
        setTimeout(() => {
            inputsRef.current[0]?.focus();
        }, 100);
        setMessage("");
        setShowResendTimer(false);


        alert(`OTP sent successfully!\n\nYour OTP is: ${newOTP}`);
    };


    // Handle input
    const handleChange = (value, index) => {
        if (!/^[0-9]?$/.test(value)) return;


        const newOtp = [...otp];
        newOtp[index] = value;
        setOtp(newOtp);


        if (value && index < otp.length - 1) {
            inputsRef.current[index + 1].focus();
        }
    };


    // Backspace
    const handleKeyDown = (e, index) => {
        if (e.key === "Backspace") {
            if (!otp[index] && index > 0) {
                inputsRef.current[index - 1].focus();
            }
        }
    };


    // Paste
    const handlePaste = (e) => {
        const pasteData = e.clipboardData.getData("text").slice(0, 4);
        if (!/^[0-9]+$/.test(pasteData)) return;


        const newOtp = pasteData.split("");
        const filledOtp = [...otp];


        newOtp.forEach((num, idx) => {
            filledOtp[idx] = num;
        });


        setOtp(filledOtp);
    };


    // Start timer function
    const startTimer = () => {
        setTimer(30);


        if (timerRef.current) clearInterval(timerRef.current);


        timerRef.current = setInterval(() => {
            setTimer((prev) => {
                if (prev === 1) {
                    clearInterval(timerRef.current);
                    return 0;
                }
                return prev - 1;
            });
        }, 1000);
    };


    // Verify OTP (fake API)
    const verifyOTP = () => {
        const enteredOtp = otp.join("");


        setLoading(true);
        setMessage("");


        setTimeout(() => {
            setLoading(false);


            if (enteredOtp === generatedOTP) {
                setMessage("OTP Verified Successfully");
                setShowResendTimer(false);
                setIsVerified(true);
            } else {
                setMessage("❌ Invalid OTP, try again");
            }
        }, 2500);
    };


    // AUTO TRIGGER when 4 digits filled
    useEffect(() => {
        if (otp.every((digit) => digit !== "") && otpSent) {
            setShowResendTimer(true);
            setTimer(30);


            const interval = setInterval(() => {
                setTimer((prev) => {
                    if (prev === 1) clearInterval(interval);
                    return prev - 1;
                });
            }, 1000);


            verifyOTP();
        }
    }, [otp]);


    return (
        <div className="main-wrapper flex flex-col items-center w-full min-h-screen px-4 md:px-8 py-8">


            <h1 className="text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
                OTP Verification Component
            </h1>


            {/* OTP INPUTS */}
            {otpSent && !isVerified && (
                <div className="flex gap-2 mb-4">
                    {otp.map((value, index) => (
                        <input
                            key={index}
                            ref={(el) => (inputsRef.current[index] = el)}
                            type="text"
                            maxLength="1"
                            value={value}
                            onChange={(e) => handleChange(e.target.value, index)}
                            onKeyDown={(e) => handleKeyDown(e, index)}
                            onPaste={handlePaste}
                            className={`w-12 h-12 text-center rounded-md text-lg transition-all duration-200 outline-none ${value
                                ? "border-2 border-pink-500"
                                : "border border-gray-300"
                                } focus:border-pink-500 focus:ring-2 focus:ring-pink-200`}
                        />
                    ))}
                </div>
            )}


            {/* SEND OTP */}
            {!otpSent && (
                <>
                    <button
                        onClick={fakeSendOTP}
                        className="bg-pink-500 text-white px-4 py-2 rounded-md cursor-pointer font-semibold"
                    >
                        Send OTP
                    </button>
                    <p className="text-sm text-slate-500 font-semibold mt-2"><i> Click this button to generate the OTP.</i> </p>
                </>
            )}


            {/* RESEND OTP */}
            {otpSent && showResendTimer && (
                <button
                    onClick={fakeSendOTP}
                    disabled={timer > 0}
                    className="text-sm text-blue-600 mt-3 disabled:opacity-50 cursor-pointer"
                >
                    {timer > 0
                        ? `Resend OTP in ${timer}s`
                        : "Resend OTP"}
                </button>
            )}


            {/* LOADING */}
            {loading && (
                <div className="mt-3 w-10 h-10 border-2 border-gray-300 border-t-pink-500 rounded-full animate-spin"></div>
            )}


            {/* MESSAGE */}
            {!loading && message && (
                <div className="mt-3 flex items-center gap-2">
                    {isVerified && <span className="text-2xl">✅</span>}


                    <p
                        className={`text-lg ${isVerified ? "text-green-500" : "text-red-500"
                            }`}
                    >
                        {message}
                    </p>
                </div>
            )}
        </div>
    );
}