skip to Main Content
export default function App() {
  const [active, setActive] = useState("");

  function handleButtonClick(value) {
    if (!active) {
      setActive(value);
      setTimeout(() => {
        setActive("");
        console.log(active);
      }, 3000);
    } else {
      let interval = setInterval(() => {
        console.log(active);
        if (!active) {
          setActive(value);
          setTimeout(() => {
            setActive("");
          }, 3000);
          clearInterval(interval);
        }
      }, 0);
    }
  }

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        height: "100vh",
        justifyContent: "space-between"
      }}
    >
      <div>
        <button onClick={() => handleButtonClick("first")}>first</button>
        <button onClick={() => handleButtonClick("second")}>second</button>
        <button onClick={() => handleButtonClick("third")}>third</button>
      </div>
      <div>{active.length > 0 && <p>{active} button Clicked</p>}</div>
    </div>
  );
}

The callback function of setInterval in else case of handleButtonClick function is not having access to updated state, if the setInterval is started and then after that state is updated then the callback function is only having access to previous state but not updated state. How can i fix this

Required functionality
when any button is clicked the active state is set to respective value and then after 3 seconds with setTimeout the state is changed to empty string as i want it to act it as a poppup.
if another button is clicked before 3 seconds then it should wait for current 3 seocnds to complete and should show the respestive button clicked popup

4

Answers


  1. The active identifier that the interval callback closes over will always be the same inside the interval – it’ll be that one const [active ... that was declared at the rendering where the handleButtonClick was created.

    It looks like you want to queue up button values pressed when one is already active – consider pushing the future values to a stateful array, and use useEffect to move the first value in that array to the active state when needed. This way, you’ll never be depending on values in a possibly stale closure.

    const { useState, useEffect } = React;
    
    function App() {
      const [active, setActive] = useState("");
      const [queue, setQueue] = useState([]);
      const toggleActive = (val) => {
        setActive(val);
        const timeoutId = setTimeout(() => {
          setActive('');
        }, 3000);
        // don't forget to clean up
        return () => clearTimeout(timeoutId);
      };
      function handleButtonClick(value) {
        if (active) {
          setQueue([...queue, value]);
        } else {
          toggleActive(value);
        }
      }
      useEffect(() => {
        // Run the next value in the queue, if active is empty and the queue is not
        if (!active && queue.length) {
          toggleActive(queue[0]);
          setQueue(queue.slice(1));
        }
      }, [active]);
      
      return (
        <div
          style={{
            display: "flex",
            flexDirection: "column",
            height: "100vh",
            justifyContent: "space-between"
          }}
        >
          <div>
            <button onClick={() => handleButtonClick("first")}>first</button>
            <button onClick={() => handleButtonClick("second")}>second</button>
            <button onClick={() => handleButtonClick("third")}>third</button>
          </div>
          <div>{active.length > 0 && <p>{active} button Clicked</p>}</div>
        </div>
      );
    }
    
    ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div class='react'></div>
    Login or Signup to reply.
  2. The required functionality is probably called ‘debounce’ and can be easily implemented in React using the ‘useEffect’ hook:

    const [active, setActive] = useState('')
    
    useEffect(() => {
      const timer = setTimeout(() => setActive(''), 3000);
      return () => clearTimeout(timer);
    }, [active]);
    
    const handleClick = (value) => {
      setActive(value);
    }
    
    Login or Signup to reply.
  3. setState (here setActive) is a async function hence updated value wont be print in console used just after it.

    you can use

    setActive(prevState => { return updatedState; }
    

    here prevState will have access to updated state

    Login or Signup to reply.
  4. Here’s an alternative approach that uses promises instead of timeout. I think you will find that the complexity is reduced. Note we are careful to avoid setState calls on unmounted components. See the You Might Not Need An Effect guide from React –

    const sleep = ms =>
      new Promise(r => setTimeout(r, ms))
    
    function App() {
      const [active, setActive] = React.useState("")
      const queue = React.useRef(Promise.resolve())
      const mounted = React.useRef(true)
      React.useEffect(_ => _ => { mounted.current = false },[])
      const alert = message => event => {
        queue.current = queue.current
          .then(_ => mounted.current && setActive(message))
          .then(_ => sleep(3000))
          .then(_ => mounted.current && setActive(""))
      }
      return (
        <div>
          <button onClick={alert("first")}>first</button>
          <button onClick={alert("second")}>second</button>
          <button onClick={alert("third")}>third</button>
          {active && <p>{active}</p>}
        </div>
      )
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search