Delay React onMouseOver event
Asked Answered
O

5

11

I have a list of elements, when hovering one of these, I'd like to change my state.

<ListElement onMouseOver={() => this.setState({data})}>Data</ListElement>

Unfortunately, if I move my mouse over the list, my state changes several times in a quick succession. I'd like to delay the change on state, so that it waits like half a second before being fired. Is there a way to do so?

Orthopedics answered 22/5, 2018 at 11:37 Comment(2)
Are you sure a delay is what you’re looking for? Couldn’t you remove the event listener then add it again when the mouse leaves? Or run a check before setting the state?Doncaster
Why not using onMouseEnter and onMouseLeave?Actinoid
G
5

You can use debounce as a dedicated package or get it from lodash, etc:

Useful for implementing behavior that should only happen after a repeated action has completed.

const debounce = require('debounce');

class YourComponent extends Component {
  constructor(props) {
    super(props);

    this.debouncedMouseOver = debounce(handleMouseOver, 200);
  }

  handleMouseOver = data => this.setState({ data });

  render() {
    const data = [];
    return <ListElement onMouseOver={() => this.debouncedMouseOver(data)}>Data</ListElement>;
  }
}
Gandhi answered 22/5, 2018 at 11:41 Comment(0)
C
20

Here's a way you can delay your event by 500ms using a combination of onMouseEnter, onMouseLeave, and setTimeout.

Keep in mind the state update for your data could be managed by a parent component and passed in as a prop.

import React, { useState } from 'react'

const ListElement = () => {
    const [data, setData] = useState(null)
    const [delayHandler, setDelayHandler] = useState(null)

    const handleMouseEnter = event => {
        setDelayHandler(setTimeout(() => {
            const yourData = // whatever your data is

            setData(yourData)
        }, 500))
    }

    const handleMouseLeave = () => {
        clearTimeout(delayHandler)
    }

    return (
        <div
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
        >
            I have a delayed event handler
        </div>
    )
}

export default ListElement
Cumin answered 19/12, 2019 at 22:16 Comment(5)
Given state doesnt change synchronously, is it possible that the delayHandler value isnt set if the user enters and leaves very quickly?Trish
Best of all answers. This should be the correct answer.Sclerotomy
Very helpful. The minimal and cleanest solution (though still requires storing two states... dealing with timeouts is always annoying)Whisky
How can i apply this to onMouseLeave having a delay? I want to have a dropdown menu linger for a couple seconds, but it doesen't seem to be working.Haupt
There's no reason to store the timeout in a useState. Store it in a useRef to avoid unnecessary rendersApposition
P
14

I might be a little late for this but I'd like to add to some of the answers above using Lodash debounce. When debouncing a method, the debounce function lets you cancel the call to your method based on some event. See example for functional component:

const [isHovered, setIsHovered] = React.useState(false)

const debouncedHandleMouseEnter = debounce(() => setIsHovered(true), 500)

const handlOnMouseLeave = () => {
  setIsHovered(false)
  debouncedHandleMouseEnter.cancel()
}

return (
  <div
    onMouseEnter={debouncedHandleMouseEnter}
    onMouseLeave={handlOnMouseLeave}
  >
   ... do something with isHovered state...
  </div>
)

This example lets you call your function only once the user is hovering in your element for 500ms, if the mouse leaves the element the call is canceled.

Perfect answered 12/7, 2021 at 15:25 Comment(0)
G
5

You can use debounce as a dedicated package or get it from lodash, etc:

Useful for implementing behavior that should only happen after a repeated action has completed.

const debounce = require('debounce');

class YourComponent extends Component {
  constructor(props) {
    super(props);

    this.debouncedMouseOver = debounce(handleMouseOver, 200);
  }

  handleMouseOver = data => this.setState({ data });

  render() {
    const data = [];
    return <ListElement onMouseOver={() => this.debouncedMouseOver(data)}>Data</ListElement>;
  }
}
Gandhi answered 22/5, 2018 at 11:41 Comment(0)
M
2

You can create a method that will trigger the onMouseOver event when matching special requirements.

In the further example, it triggers after 500 ms.

/**
 * Hold the descriptor to the setTimeout
 */
protected timeoutOnMouseOver = false;

/**
 * Method which is going to trigger the onMouseOver only once in Xms
 */
protected mouseOverTreatment(data) {
   // If they were already a programmed setTimeout
   // stop it, and run a new one
   if (this.timeoutOnMouseOver) {
     clearTimeout(this.timeoutOnMouseOver);
   }

   this.timeoutOnMouseOver = setTimeout(() => {
      this.setState(data);

      this.timeoutOnMouseOver = false;
   }, 500);
}
Maragaret answered 22/5, 2018 at 11:45 Comment(0)
C
1

debounce is always the answer if you want to limit the action in a time frame.

Implementation is simple, no need for external libraries.

implementation:

type Fnc = (...args: any[]) => void;

// default 300ms delay
export function debounce<F extends Fnc>(func: F, delay = 300) {
    type Args = F extends (...args: infer P) => void ? P : never;
    let timeout: any;
    return function (this: any, ...args: Args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

usage:

...

/** any action you want to debounce */
function foo(
    data: any, 
    event: React.MouseEvent<HTMLDivElement, MouseEvent>
): void {
    this.setState({data});
}

const fooDebounced = debounce(foo, 500);

<ListElement onMouseOver={fooDebounced.bind(null, data)}>
    Data
</ListElement>
...

You don't actually have to bind a function, but it's a good habit if you loop through multiple elements to avoid initializing a new function for each element.

Citronellal answered 15/3, 2021 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.