Search code examples
javascriptreactjsreact-hookstimeout

React hooks - Set a timeout inside useEffect but be able to clear it from a mouse event?


This is a classic menu case, open on a button click, hide in 5 seconds if no activity. We have 2 state variables. open and active, corresponding to two different states = menu is open, and active (being used). I set the open variable using a button click, then in effect I start the 5 second timeout. Now if the user mouseovers the menu,I set the active property to true and try to clear the timeout. But that is not working. Meaning, the timeout var is always null inside the code that is supposed to clear it.

Here is some code to help you understand:

let [open, openMenu] = useState(false)
let [active, activateMenu] = useState(false)

let timer = null;
useEffect(() => {
    if(open && !active) {
        timer = setTimeout(() => setOpen(false), 5000)
    }

    if(open && active) {
        if(timer) {
            clearTimeout(timer)
        }
    }
}, [open, active])

// Triggered via the UI on a button click
openMenuhandler() {
    setOpen(true)
}

// Triggered via the UI on mouseenter
// identifies that the menu is being used, when mouse is over it
setMenuActive() {
    activateMenu(true)
}

// Triggered via the UI on mouseleave
setMenuInActive() {
    activateMenu(false)
}

// other code here on

Now the timeout is never cleared. The menu hides in 5 seconds, no matter what. Is there another way to do it? I have even tried moving the clearTimeout to the mouseLeave, but even then the timer is null. How to fix this? Please help.


Solution

  • Whenever your component re-renders, timer variable will be re-declared with an initial value of null. As a result, when useEffect hook executes whenever any of its dependency changes, timer variable is null.

    You can solve the problem by making sure that value of the timer variable is persisted across re-renders of your component. To persist the value across re-renders, either save the id of the setTimeout using useRef hook or save it in the state, i.e. useState hook.