skip to Main Content

I’m creating a custom popup in React that shows up after a button click, and I want to go away from the screen after X amount of seconds. The issue I’m running into is that if you click the button multiple times before the initial timeout ends, you get very strange behavior like the popup jumping in and out or just not showing up at all.

I realize that this is due to me misusing React state, but I am not sure how to get around this issue. Can somebody please help me?

function popupHandler(popupText, setPopupVis, setPopupText) {
    setPopupText(popupText)
    setPopupVis("visible");
    // Works when you click a button ONCE and then don't touch anything during the timeout windows
    // Totally breaks if you do otherwise. 
    // I want to "extend" the timeout so that the 
    //  popup window keeps staying up if the button keeps getting clicked
    setTimeout(() => {
        setPopupVis("hidden")
    }, 3000);
}

function CustomButton({setPopupText, setPopupVis}) {
    return (
        <button onClick={() => popupHandler("Dynamic string", setPopupText, setPopupVis)}>
            I am a button
        </button>
    )
}

function MyPopUp({popupVis, popupText}) {
    return (
        <div style={{visibility: popupVis}}>
            {popupText}
        </div>
    )
}

export default function MyContent() {
    const [popupVis, setPopupVis] = useState('hidden');
    const [popupText, setPopupText] = useState('')
    return (
        <>
            <CustomButton setPopupText={setPopupText} setPopupVis={setPopupVis} />
            <MyPopUp popupVis={popupVis} popupText={popupText} />
        </>
    )
}

3

Answers


  1. Chosen as BEST ANSWER

    You can get the required functionality by adding another state variable that keeps track of your setTimeout() timers.

    import React, { useState } from "react";
    
    function popupHandler(popupText, setPopupVis, setPopupText, popupTimer, setPopupTimer) {
      setPopupText(popupText);
      setPopupVis("visible");
      // YOU NEED THIS
      if (popupTimer) {
        clearTimeout(popupTimer);
      }
      setPopupVis("visible");
      const newTimer = setTimeout(() => {
          setPopupVis("hidden");
      }, 5000);
      setPopupTimer(newTimer);
    }
    
    function CustomButton({ setPopupText, setPopupVis, popupTimer, setPopupTimer}) {
      return (
        <button
          onClick={() => popupHandler("Dynamic string", setPopupText, setPopupVis, popupTimer, setPopupTimer)}
        >
          I am a button
        </button>
      );
    }
    
    function MyPopUp({ popupVis, popupText }) {
      return <div style={{ visibility: popupVis }}>{popupText}</div>;
    }
    
    export default function MyContent() {
      const [popupVis, setPopupVis] = useState("hidden");
      const [popupText, setPopupText] = useState("");
      // YOU NEED THIS
      const [popupTimer, setPopupTimer] = useState(null);
    
      return (
        <>
          <CustomButton setPopupText={setPopupText} setPopupVis={setPopupVis} popupTimer={popupTimer} setPopupTimer={setPopupTimer} />
          <MyPopUp popupVis={popupVis} popupText={popupText} />
        </>
      );
    }
    

  2. The issue you’re facing stems from the fact that each time the button is clicked, a new timeout is set without clearing the previous one. This leads to unexpected behavior, especially when rapid clicks occur. To address this, you can use the clearTimeout function to clear the previous timeout before setting a new one. Here’s how you can modify your code to handle this:

    import React, { useState } from "react";
    
    function popupHandler(popupText, setPopupVis, setPopupText) {
      setPopupText(popupText);
      setPopupVis("visible");
    
      // Clear any existing timeout
      if (popupHandler.timerId) {
        clearTimeout(popupHandler.timerId);
      }
    
      // Set a new timeout
      popupHandler.timerId = setTimeout(() => {
        setPopupVis("hidden");
      }, 3000);
    }
    
    function CustomButton({ setPopupText, setPopupVis }) {
      return (
        <button
          onClick={() => popupHandler("Dynamic string", setPopupText, setPopupVis)}
        >
          I am a button
        </button>
      );
    }
    
    function MyPopUp({ popupVis, popupText }) {
      return <div style={{ visibility: popupVis }}>{popupText}</div>;
    }
    
    export default function MyContent() {
      const [popupVis, setPopupVis] = useState("hidden");
      const [popupText, setPopupText] = useState("");
    
      return (
        <>
          <CustomButton setPopupText={setPopupText} setPopupVis={setPopupVis} />
          <MyPopUp popupVis={popupVis} popupText={popupText} />
        </>
      );
    }
    

    In this modification, I’ve introduced a timerId property attached to the popupHandler function to keep track of the current timeout. Before setting a new timeout, it checks if there’s an existing timeout and clears it using clearTimeout. This ensures that only one timeout is active at any given time, preventing the erratic behavior you were experiencing with rapid button clicks.

    Login or Signup to reply.
  3. give popupHandler acces to popusVis. if popupVis is already "visible," return early.

    function popupHandler(popupVis, popupText setPopupVis, setPopupText) {
    //return early if it's already visible
    if (popupVis === "visible") return
    
    // yourcode
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search