Reputation: 1879
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?
Upvotes: 12
Views: 24007
Reputation: 1714
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.
Upvotes: 1
Reputation: 291
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.
Upvotes: 14
Reputation: 209
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
Upvotes: 20
Reputation: 23515
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);
}
Upvotes: 2
Reputation: 152216
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>;
}
}
Upvotes: 6