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

Tell us what’s happening:

I’m currently trying to make a countdown timer that actively updates the paragraph element with the ‘#otp-timer’ id. However, I am unsure how to display the amount of seconds left with each second passing by. I am only able to display the updated message after 5 seconds and even then it just happens without me clicking the button.

I’m aware I’m nowhere near finished and just want to focus on this feature at the moment.

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 */

/* file: index.jsx */
const { useState, useEffect, useRef } = React;

export const OTPGenerator = () => {
  const [timerMsg, setTimerMsg] = useState('');

  useEffect(() => {
    const countDownTimer = setTimeout(() => {
      setTimerMsg("OTP expired. Click the button to generate a new OTP.");
    }, 5000);

    return () => clearTimeout(countDownTimer);
  });

  return (
    <div className="container">
      <h1 id="otp-title">OTP Generator</h1>
      <h2 id="otp-display">Click 'Generate OTP' to get a code</h2>
      <p id="otp-timer" aria-live=''>{timerMsg}</p>
      <button onClick={useEffect} id="generate-otp-button">Generate OTP</button>
    </div>
  );
};

Challenge Information:

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

You can think of effect as something that can run without any user action (maybe a bit like event listener). Using button to trigger effect can be done, but not directly - that’s not how effects work - using useEffect as on-click callback will only call the useEffect function, not the effect which was earlier defined.

However, pressing the button could change something that would trigger the effect.

1 Like

Still a little confused here. I wouldn’t be able to pass in anything else as a function for the onClick event handler or rather prop as thats what its referred to. For instance, countDownTimer is out of scope. Unless you’re referring to something not currently present in the program within it’s current state.

Would it have to deal with dependencies?

ADDITION:

I made the following changes:

const { useState, useEffect, useRef } = React;




export const OTPGenerator = () => {

  const [count, setCount] = useState(5);

  const [timerMsg, setTimerMsg] = useState('');




  useEffect(() => {

    // setCount(count - 1);




    const countDownTimer = setTimeout(() => {

      setTimerMsg("OTP expired. Click the button to generate a new OTP.");

    }, 5000);




    return () => clearTimeout(countDownTimer);

  }, [count]);




  return (

    <div className="container">

      <h1 id="otp-title">OTP Generator</h1>

      <h2 id="otp-display">Click 'Generate OTP' to get a code</h2>

      <p id="otp-timer" aria-live=''>{timerMsg}</p>

      <button onClick={() => setTimerMsg(`Expires in: ${count} seconds`)} id="generate-otp-button">Generate OTP</button>

    </div>

  );

};

However, it’s not letting me use the setter function for the count state variable. It throws the following error in the console:

It definitely can be confusing.

Other functions which are in the scope, can be used as callbacks for click, but yes, in current code there isn’t defined any that would do something useful.

Notice that when setCount(count - 1) is not commented, each change of the count will right now trigger another change of the count. This is how effect with dependency works - every change to the dependency will run the effect. Therefore this ends up a bit like infinite recursion.

1 Like

Ah, I get it now. So, I tried to create another state variable that represents the seconds being ticked down. The count state variable, which is used as a dependency, is no longer being altered within the useState hook. I added some comments in my code to help with clarity.

The current issue is that the timer state variable is not being rendered every passing second or possibly even changing every passing second within the handleTimer() function. It should tick down from 5 to 0 then the state variable’s value will be reset back to 5 in case the user clicks the button again. That way it ticks down from 5 rather than going into the negatives.

Code:

const { useState, useEffect, useRef } = React;




export const OTPGenerator = () => {

  const [count, setCounter] = useState(0);

  const [timer, setTimer] = useState(5);

  const [timerMsg, setTimerMsg] = useState('');




  useEffect(() => {

    // setTimer(timer - 1);




    const countDownTimer = setTimeout(() => {

      setTimerMsg("OTP expired. Click the button to generate a new OTP.");




      // Once expiration message is displayed, reset the state variable's value back to 5

      setTimer(timer + 5);

    }, 5000);




    return () => clearTimeout(countDownTimer);

  }, [count]);




  function handleTimer() {

    setTimerMsg(`Expires in: ${timer} seconds`);

    // Update count since its a dependency and a change will cause the useEffect() hook to run again

    setCounter(count + 1);




    // Update the timer state variable every second to decrement it from 5 to 0 before displaying expiration message.

    setInterval(() => setTimer(timer - 1), 1000);

  }




  return (

    <div className="container">

      <h1 id="otp-title">OTP Generator</h1>

      <h2 id="otp-display">Click 'Generate OTP' to get a code</h2>

      <p id="otp-timer" aria-live=''>{timerMsg}</p>

      <button onClick={handleTimer} id="generate-otp-button">Generate OTP</button>

    </div>

  );

};

I made a post on Stack Overflow and someone was able to help me figure it out with their approach.