How do you perform debounce in React.js?
I want to debounce the handleOnChange.
I tried with debounce(this.handleOnChange, 200)
but it doesn't work.
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
var SearchBox = React.createClass({
render: function() {
return <input type="search" name="p" onChange={this.handleOnChange} />;
},
handleOnChange: function(event) {
// make ajax call
}
});
Best Answer
2019: try hooks + promise debouncing
This is the most up to date version of how I would solve this problem. I would use:
This is some initial wiring but you are composing primitive blocks on your own, and you can make your own custom hook so that you only need to do this once.
And then you can use your hook:
You will find this example running here and you should read react-async-hook documentation for more details.
2018: try promise debouncing
We often want to debounce API calls to avoid flooding the backend with useless requests.
In 2018, working with callbacks (Lodash/Underscore) feels bad and error-prone to me. It's easy to encounter boilerplate and concurrency issues due to API calls resolving in an arbitrary order.
I've created a little library with React in mind to solve your pains: awesome-debounce-promise.
This should not be more complicated than that:
The debounced function ensures that:
this.setState({ result });
will happen per API callEventually, you may add another trick if your component unmounts:
Note that Observables (RxJS) can also be a great fit for debouncing inputs, but it's a more powerful abstraction which may be harder to learn/use correctly.
< 2017: still want to use callback debouncing?
The important part here is to create a single debounced (or throttled) function per component instance. You don't want to recreate the debounce (or throttle) function everytime, and you don't want either multiple instances to share the same debounced function.
I'm not defining a debouncing function in this answer as it's not really relevant, but this answer will work perfectly fine with
_.debounce
of underscore or lodash, as well as any user-provided debouncing function.GOOD IDEA:
Because debounced functions are stateful, we have to create one debounced function per component instance.
ES6 (class property): recommended
ES6 (class constructor)
ES5
See JsFiddle: 3 instances are producing 1 log entry per instance (that makes 3 globally).
NOT a good idea:
It won't work, because during class description object creation,
this
is not the object created itself.this.method
does not return what you expect because thethis
context is not the object itself (which actually does not really exist yet BTW as it is just being created).NOT a good idea:
This time you are effectively creating a debounced function that calls your
this.method
. The problem is that you are recreating it on everydebouncedMethod
call, so the newly created debounce function does not know anything about former calls! You must reuse the same debounced function over time or the debouncing will not happen.NOT a good idea:
This is a little bit tricky here.
All the mounted instances of the class will share the same debounced function, and most often this is not what you want!. See JsFiddle: 3 instances are producting only 1 log entry globally.
You have to create a debounced function for each component instance, and not a single debounced function at the class level, shared by each component instance.
Take care of React's event pooling
This is related because we often want to debounce or throttle DOM events.
In React, the event objects (i.e.,
SyntheticEvent
) that you receive in callbacks are pooled (this is now documented). This means that after the event callback has be called, the SyntheticEvent you receive will be put back in the pool with empty attributes to reduce the GC pressure.So if you access
SyntheticEvent
properties asynchronously to the original callback (as may be the case if you throttle/debounce), the properties you access may be erased. If you want the event to never be put back in the pool, you can use thepersist()
method.Without persist (default behavior: pooled event)
The 2nd (async) will print
hasNativeEvent=false
because the event properties have been cleaned up.With persist
The 2nd (async) will print
hasNativeEvent=true
becausepersist
allows you to avoid putting the event back in the pool.You can test these 2 behaviors here: JsFiddle
Read Julen's answer for an example of using
persist()
with a throttle/debounce function.