Build a One-Time Password Generator - Build a One-Time Password Generator

Tell us what’s happening:

hi everyone, my code is failing the tests 12 and 13 but my countdown is working exactly the same as in the example project. when the button is clicked the count is setted to 5 the decrement every second to 0 with interval, then the interval is cleared at the 5th second, i verified the texts also for any missing character or something like that but didnt found anything, any help please.

Your code so far

<!-- file: index.html -->
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>OTP Generator</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
    <script
      data-plugins="transform-modules-umd"
      type="text/babel"
      src="index.jsx"
    ></script>
</head>

<body>
    <div id="root"></div>
    <script
      data-plugins="transform-modules-umd"
      type="text/babel"
      data-presets="react"
      data-type="module"
    >
      import { OTPGenerator } from './index.jsx';
      ReactDOM.createRoot(document.getElementById('root')).render(<OTPGenerator />);
    </script>
</body>

</html>
/* file: styles.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  borde: 1px solid black;
}

body {
  color: #222129;
  font-family: "Segoe UI", sans-serif;
  font-size: 1.1rem;
  width: 100%;
  height: 100vh;
}

h1 {
  text-align: center;
  margin-top: 60px;
}

h2 {
  color: #5c5b63;
  margin-top: 20px;
  text-align: center;
}

p {
  text-align: center;
  font-weight: bold;
  color: #5c5b63;
  margin-top: 20px;
}

button {
  display: block;
  margin: 0 auto;
  margin-top: 20px;
  border: none;
  color: white;
  padding: 10px 25px;
  background-color: #383463;
  border-radius: 6px;
  font-size: inherit;
}

button:not([disabled]):hover {
  background-color: grey !important;
}
/* file: index.jsx */
const { useState, useEffect, useRef } = React;

export const OTPGenerator = () => {
  const [otp, setOtp] = useState("");
  const [count, setCount] = useState(0);
  console.log(count);

  useEffect(() => {
    if (!otp) {
      return;
    }
    
    setCount(5);
    const intervalId = setInterval(() => {
      setCount(prevCount => prevCount - 1);
    }, 1000);
    const timeoutId = setTimeout(() => {
      clearInterval(intervalId);
    }, 5000);

    return () => {
      clearInterval(intervalId);
      clearTimeout(timeoutId);
    }
  }, [otp]);

  function handleOtp() {
    let array = [];
    for (let i = 0; i < 6; i++) {
      array.push(Math.floor(Math.random() * 10));
    }
    setOtp(array.join(""));
  }

  return (
    <div className="container">
      <h1 id="otp-title">OTP Generator</h1>
      <h2 id="otp-display">{otp ? otp : "Click 'Generate OTP' to get a code"}</h2>
      <p id="otp-timer" aria-live="polite">{otp && (count ? `Expires in: ${count} seconds` : "OTP expired. Click the button to generate a new OTP.")}</p>
      <button id="generate-otp-button" onClick={handleOtp} style={count !== 0 ? {backgroundColor: "grey"} : {backgroundColor: "#383463"}} disabled={count !== 0}>Generate OTP</button>

    </div>
  );
};

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36

Challenge Information:

Build a One-Time Password Generator - Build a One-Time Password Generator

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-one-time-password-generator/67c562286b29447da020d407.md at main · freeCodeCamp/freeCodeCamp · GitHub

Hi @hocineneder7

Try using the useState hook with the button disabled attribute.

Happy coding

const { useState, useEffect, useRef } = React;

export const OTPGenerator = () => {
  const [otp, setOtp] = useState("");
  const [count, setCount] = useState(0);
  console.log(count);
  const [isDisabled, setIsDisabled] = useState(false);

  useEffect(() => {
    if (!otp) {
      return;
    }
    
    setCount(5);
    const intervalId = setInterval(() => {
      setCount(prevCount => prevCount - 1);
    }, 1000);
    const timeoutId = setTimeout(() => {
      clearInterval(intervalId);
      setIsDisabled(false);
    }, 5000);

    return () => {
      clearInterval(intervalId);
      clearTimeout(timeoutId);
    }
  }, [otp]);

  function handleOtp() {
    let array = [];
    for (let i = 0; i < 6; i++) {
      array.push(Math.floor(Math.random() * 10));
    }
    setIsDisabled(true);
    setOtp(array.join(""));
  }

  return (
    <div className="container">
      <h1 id="otp-title">OTP Generator</h1>
      <h2 id="otp-display">{otp ? otp : "Click 'Generate OTP' to get a code"}</h2>
      <p id="otp-timer" aria-live="polite">{otp && (count ? `Expires in: ${count} seconds` : "OTP expired. Click the button to generate a new OTP.")}</p>
      <button id="generate-otp-button" onClick={handleOtp} style={count !== 0 ? {backgroundColor: "grey"} : {backgroundColor: "#383463"}} disabled={isDisabled}>Generate OTP</button>

    </div>
  );
};

i tried doing this but the tests still failing

I’ve found the solution

The click handler set otp but not count. count only got set to 5 later, inside a useEffect that fires after the click’s render. That created one extra render where otp was already set but count was still 0, so the timer text briefly showed “OTP expired…” instead of “Expires in: 5 seconds” — and the tests caught that render.

so i moved setCount(5) from useEffect to handleOtp to not run into that extra render