[ES5 & ES6] Debounce React Events on Inputs

In my time, I've written a lot of React code, and one of the most common requirements I come across are the ability to update components when a user has finished doing something. For example, a table with search and sort options. The sorting should use onClick and run immediately, but if you've got 10k+ rows of data, searching every keypress is a pain. The way we get around that is by using a debounce, and the code for a basic version of how we do this is shown below.

The Debounce Function

This is a nice little debouncer which runs in a nice, performant manner. There's a lot out there though, and this isn't the interesting part really. However, if you want an explanation of what it's doing, there'll be one just below...

Minified Debounce

function debounce(a,b,c){var d,e;return function(){function h(){d=null,c||(e=a.apply(f,g))}var f=this,g=arguments;return clearTimeout(d),d=setTimeout(h,b),c&&!d&&(e=a.apply(f,g)),e}}

Explanation

The arguments this takes are:

a. A function to be debounced
b. A period of time to wait before debouncing
c. A controller for whether to call the function before or after the timer. If falsey, the function waits for the timer to run down. Otherwise, it gets called first, and then waits to run again until the timer is hit

The function itself makes use of the apply method, but that's about the only vaguely interesting thing here. (apply allows for a function to be called with a defined context). If you want more information on how this works, there's a good explanation here.

Debouncing Events

Now, here's two implementations of the component code.

ES5

var Table = React.createClass({
    getInitialState: function (props) {
        return { searchTerm: props.searchTerm }
    },
    componentWillMount: function() {
        this.setSearchTerm = debounce(this.method, 1000)
    },
    setSearchTerm: function (searchTerm) {
        this.setState({ searchTerm: searchTerm })
    },
    render: function () {
        return (
            <div>
                <p>{this.state.searchTerm}</p>
                <input type="search" onChange: function onChange(e) { this.setSearchTerm(e.target.value) } />
            </div>
        )
    }
})

ES6

class Table extends React.Component {
    constructor (props) {
        super(props)
        this.state = { searchTerm: props.searchTerm }
    }

    setSearchTerm = debounce(searchTerm => {
        this.setState({ searchTerm })
    }, 1000)

    render() {
        return (
            <div className="widget">
                <p>{this.state.searchTerm}</p>
                <input onChange={e => {this.setSearchTerm(e.target.value)}} />
            </div>
        )
    }
}

Why This Way

By having a specific function in the scope of the component, we can create the component multiple times, and each debounce will be separate, containing the ability for typing in one place to leak the event to other instances of the block in question. It also means that the debounce function itself is only set up once, so rather than every keypress running it, we simply have it run once and stay around for as long as the component is mounted.

Finally, having the onChange event send the value along means that even when the event is returned to the pool, it doesn't matter, as we've already sent the value required along. Thus we gain the benefits of the pool cleaning React does automatically, and don't interfere there by using event.persist, but also get the right value set.

Demo

You can see a demo of the ES6 example running at this JSfiddle.

If you've enjoyed this post, you might want to follow our founder, Pete Watson-Wailes on Twitter