Before I realized I had started my journey towards a career in software development, I worked for an escape room designing and maintaining automation systems.
There was a puzzle that incorporated antique elevator buttons. In order to solve the puzzle, the buttons had to be pressed in a specific order. An Arduino microcontroller was listening to the electrical signals from the switches. When the buttons were pressed in the correct order, a secret door would open.
As always, there was a bug. With each press, the Arduino would detect a ripple of current and register multiple events even if the button was only pressed once. This made the puzzle impossible to solve.
The buttons needed to be debounced! Each ripple of current needed to be interpreted as a single event.
In JavaScript development, I’ve encountered analogous situations. Computers are capable of reacting to user input much quicker than what may be considered desirable. Expensive functions may be triggered too often. These issues can be solved with rate-limiting function decorators. In this post, I’ll explain why, when, and how to use debounce.
Rate-Limiting Function Decorators
A function decorator accepts an input function and returns an altered version of that same function. For example, the native JavaScript bind method, which returns a function bound to a specific context, is perhaps the most common function decorator used in JavaScript. Rate-limiting means reduces the frequency or total number of times that a given function can be called.
Debounce is a rate-limiting function decorator. Although many forms of it could be derived, I will be using the Lodash version in my React example below.
Lodash dot Debounce
_.debounce requires a function as the first parameter and a wait time in milliseconds as the second. An options object can be passed as a third argument.
_.debounce(func, wait, options);
By default, if no options are passed, _.debounce will return a function that when called will execute after the given wait period only if no other invocations of that same function have been made. If another invocation is made, then the wait time resets. See this wonderful visualization by David Corbacho.
He also wrote a fantastic article where he examining the differences between debounce and a similar function decorator, throttle.
This is known as the trailing edge implementation of debounce.
Another option is to call the function on the leading edge of a repetitive event. The terms leading and trailing edges are inherited from the application of debouncing an electrical signal like I discussed in the introduction.
If a function is invoked on the leading edge, then it will be invoked immediately. Any invocations made in the wait time afterwards will be ignored or debounced. To set this option in lodash, simply pass the object { leading: true, trailing: false } as the third argument. If both options are set to true, the function will be invoked on the trailing edge only if it is called more than once during the wait. This could be useful for implementing an event upon a double mouse click.
Debounce a Method in React
Recently, I was building a React project that mimicked YouTube using the YouTube Data API. I wanted to incorporate a search bar that would search automatically when a user stopped typing. Let’s debounce!
First I created the skeleton for my search bar component which will maintain the state of the search query in the input form.
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
searchQuery: '',
};
}
render() {
}
}
Next I built out my render method. I attached the synthetic event handler onChange to the input form and call a method to handle any changes made to the input form.
render() {
return (
<div className="search-bar form-inline" >
<input className="form-control" type="text" onChange={this.handleChange} />
</div>
);
}
Then, I needed to define the handleChange method by adding it to my component class. This will set the state of the search query to the current value of the input form. Once the state is updated, it will call the delaySearch method.
handleChange(event) {
this.setState({
searchQuery: event.target.value,
}, this.delaySearch);
}
Just below my handleChange method, I defined delaySearch which simply calls the function searchYoutube that I passed in as a prop. This function accepts the state of the search query and performs my AJAX request.
delaySearch() {
this.props.searchYoutube(this.state.searchQuery);
}
Finally, I need to bind my methods inside the constructor of the Search component. Here is where I debounce the delaySearch method to call only once every second on the trailing edge. This allows the user to finish typing their search query before a search is made. Since I want the debounced function to invoke on the trailing edge, and this is the default option in Lodash, I don't need to provide an option.
this.delaySearch = _.debounce(this.delaySearch.bind(this), 1000);
this.handleChange = this.handleChange.bind(this);
tldr
Debounce is a useful, rate-limiting, function decorator. In the Lodash version of debounce, you have the option of invoking the debounced function on the leading or trailing edge. To debounce a method in React, simply set the method equal to a debounced version when binding your method in the constructor.
Top comments (0)