How can I perform a debounce?
Asked Answered
J

48

692

How do you perform debounce in React?

I want to debounce the handleOnChange function.

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
  }
});
Jeromejeromy answered 17/4, 2014 at 1:41 Comment(3)
I met the same problem with you, superb answers below!but I think you used wrong way of debounce. here, when onChange={debounce(this.handleOnChange, 200)}/>, it will invoke debounce function every time. but ,in fact, what we need is invoke the function what debounce function returned.Herefordshire
1kb HOC - React-BouncerConfutation
What is 'handleOnChange'? An event handler? Something else?Cannabis
C
995

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.

// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {

  // Handle the input text state
  const [inputText, setInputText] = useState('');

  // Debounce the original search async function
  const debouncedSearchFunction = useConstant(() =>
    AwesomeDebouncePromise(searchFunction, 300)
  );

  // The async callback is run each time the text changes,
  // but as the search function is debounced, it does not
  // fire a new request on each keystroke
  const searchResults = useAsync(
    async () => {
      if (inputText.length === 0) {
        return [];
      } else {
        return debouncedSearchFunction(inputText);
      }
    },
    [debouncedSearchFunction, inputText]
  );

  // Return everything needed for the hook consumer
  return {
    inputText,
    setInputText,
    searchResults,
  };
};

And then you can use your hook:

const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))

const SearchStarwarsHeroExample = () => {
  const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
  return (
    <div>
      <input value={inputText} onChange={e => setInputText(e.target.value)} />
      <div>
        {searchResults.loading && <div>...</div>}
        {searchResults.error && <div>Error: {search.error.message}</div>}
        {searchResults.result && (
          <div>
            <div>Results: {search.result.length}</div>
            <ul>
              {searchResults.result.map(hero => (
                <li key={hero.name}>{hero.name}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
};

You will find this example running here and you should read the 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.js) 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:

const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));

const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);

class SearchInputAndResults extends React.Component {
  state = {
    text: '',
    results: null,
  };

  handleTextChange = async text => {
    this.setState({ text, results: null });
    const result = await searchAPIDebounced(text);
    this.setState({ result });
  };
}

The debounced function ensures that:

  • API calls will be debounced
  • the debounced function always returns a promise
  • only the last call's returned promise will resolve
  • a single this.setState({ result }); will happen per API call

Eventually, you may add another trick if your component unmounts:

componentWillUnmount() {
  this.setState = () => {};
}

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.js 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

class SearchBox extends React.Component {
    method = debounce(() => {
      ...
    });
}

ES6 (class constructor)

class SearchBox extends React.Component {
    constructor(props) {
        super(props);
        this.method = debounce(this.method.bind(this),1000);
    }
    method() { ... }
}

ES5

var SearchBox = React.createClass({
    method: function() {...},
    componentWillMount: function() {
       this.method = debounce(this.method.bind(this),100);
    },
});

See JSFiddle: three instances are producing one log entry per instance (that makes three globally).


Not a good idea:
var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: debounce(this.method, 100);
});

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 the this context is not the object itself (which actually does not really exist yet BTW as it is just being created).


Not a good idea:
var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: function() {
      var debounced = debounce(this.method,100);
      debounced();
  },
});

This time you are effectively creating a debounced function that calls your this.method. The problem is that you are recreating it on every debouncedMethod 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:
var SearchBox = React.createClass({
  debouncedMethod: debounce(function () {...},100),
});

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: three instances are producing only one 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 the persist() method.

Without persist (default behavior: pooled event)
onClick = e => {
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

The second (async) will print hasNativeEvent=false, because the event properties have been cleaned up.

With persist
onClick = e => {
  e.persist();
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

The second (async) will print hasNativeEvent=true, because persist allows you to avoid putting the event back in the pool.

You can test these two behaviors here: JSFiddle

Read Julen's answer for an example of using persist() with a throttle/debounce function.

Clemons answered 20/1, 2015 at 13:37 Comment(24)
Superb answer, this is great for setting form fields state as 'interacting' for a few seconds after they stop typing, and then being able to cancel on form submit or onBlurGand
Note that in ES6, instead of defining your method inside the constructor (feels weird) you can do handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout) at the top level of your class. You're still effectively setting an instance member but it looks a bit more like a normal method definition. No need for a constructor if you don't already have one defined. I suppose it's mostly a style preference.Elam
Don't forget to cancel the debounced method in componentWillUnmount: this.method.cancel() - otherwise it might want to setState on an unmounted component.How
Perhaps add information about stateless components (which are just functions). I guess in that case things are different?Decent
@JonasKello you can't debounce inside a stateless component because the debounced function is actually stateful. You need a stateful component to hold that debounced function, but you can call a stateless component with an already debounced function if needed.Clemons
Why all answer includes _.debounce instead of writing the function ? It needs the whole library for that function ?Kirby
@Kirby you don't need underscore to use this answer. I use underscore/lodash _ just because people are used to these libs for debouncing/throttling, but you can provide your own debouncing function and the answer stays the same. I'm removing the _ for clarityClemons
I would highly recommend editing this answer and showing imports contrary to what you say it is crucial and I'm not able to implement your answer in my code as is.Africanist
It this.method = debounce(this.method,1000); should not work. You should bind this method to this (sic!): this.method = debounce(this.method.bind(this),1000);Incalescent
@IgorIlić if you are using ES5 createClass syntax the method is already bound to this. Maybe you tried to mix 2 of the proposed solutions in an incorrect way? please share a jsfiddle if you still have this error.Clemons
Excellent note on React's event pooling! Is there any concern with e.persist() of memory leakage? Do you need to handle GC here manually? If so, what is a good way to do that?Surmise
@JonnyAsmar you don't need to do anything, the object is removed from the pool and it will just be garbage collected when it's not referenced anymore like any object, instead of being reused. As far as I know there's no imperative way to tell React to put back the event object in the pool and it's managed automatically for you.Clemons
@SebastienLorber Sorry, I am using es6 class syntax.Incalescent
@SebastienLorber can this lib be used with react-native ?Teamster
I think there is a small mistake in the hooks example. It should be const [inputText, searchStarwarsHero] = useState('');Polarity
@J.Hesters reviewed it and for me there is nothing wrong. If you still think I'm wrong can you provide a gist with more detailed explainations?Clemons
@SebastienLorber you are correct, I was tired yesterday, sorry. Great answer!Polarity
@SebastienLorber Thanks for this awesome answer. Although under the ES6 (Constructor) section where you have this.method = debounce(this.method,1000).. and then show method() below it. If you aren't binding this here, wouldn't you need to use an arrow function to define method() below? Someone commented on it above and your repsonse was if you were using ES5 createClass, but this is ES6 class syntax, a little confused by that. Thanks man!Washin
@Washin yeah you are probably write i should bind to this before deboucing (only if this is used inside method i guess, but it's often the case). Great to know my answer was helpfulClemons
@SebastienLorber As I checked, seem like searchStarwarsHero is undefined in your code. Can you double-check it again?Lemon
I just copy-pasted your solution without even thinking about it. and it's working like a charm 😋Mauritamauritania
about 2019 example, what if searchFunction change?Mango
@Mango you can add it to a ref and keep that ref up-to-date in a useLayoutEffect, it's annoying but it's a common pattern in react hooksClemons
useConstant did not work in our case. I used plain ol' useMemo() and it works fine.Affirmation
M
233

Uncontrolled Components

You can use the event.persist() method.

An example follows using Underscore.js' _.debounce():

var SearchBox = React.createClass({

  componentWillMount: function () {
     this.delayedCallback = _.debounce(function (event) {
       // `event.target` is accessible now
     }, 1000);
  },

  onChange: function (event) {
    event.persist();
    this.delayedCallback(event);
  },

  render: function () {
    return (
      <input type="search" onChange={this.onChange} />
    );
  }

});

See this JSFiddle.


Controlled Components

The example above shows an uncontrolled component. I use controlled elements all the time so here's another example of the above, but without using the event.persist() "trickery".

A JSFiddle is available as well. Example without underscore

var SearchBox = React.createClass({
    getInitialState: function () {
        return {
            query: this.props.query
        };
    },

    componentWillMount: function () {
       this.handleSearchDebounced = _.debounce(function () {
           this.props.handleSearch.apply(this, [this.state.query]);
       }, 500);
    },

    onChange: function (event) {
      this.setState({query: event.target.value});
      this.handleSearchDebounced();
    },

    render: function () {
      return (
        <input type="search"
               value={this.state.query}
               onChange={this.onChange} />
      );
    }
});


var Search = React.createClass({
    getInitialState: function () {
        return {
            result: this.props.query
        };
    },

    handleSearch: function (query) {
        this.setState({result: query});
    },

    render: function () {
      return (
        <div id="search">
          <SearchBox query={this.state.result}
                     handleSearch={this.handleSearch} />
          <p>You searched for: <strong>{this.state.result}</strong></p>
        </div>
      );
    }
});

React.render(<Search query="Initial query" />, document.body);
Macnamara answered 10/7, 2014 at 14:40 Comment(18)
This does not work for inputs. The event target in the debounced function no longer has a value... so the input stays empty.Glint
nevermind, it works in a jsfiddle, just not in my env for some reason... sorry. I edited your answer to add the jsfiddle, but I can't upvote until you edit your answer..Glint
@Glint If you are looking for a more complex example, check out (using RxJS): github.com/eliseumds/react-autocompleteGoodspeed
Slightly complex, this. You have to be a bit careful about props. If you set <input value={this.props.someprop}... then it won't render properly as the update on keypress doesn't make it back into the component until after the debounce. It's fine to omit the value= if you're happy for this to be unmanaged, but if you'd like to pre-populate the value and/or bind it somewhere else then obviously this doesn't work.Undies
@AlastairMaw the question had an uncontrolled component, that's why the reply has it too. I've added below an alternative version for controlled components, with a pre-populated value.Macnamara
this is very dangerous if you mount the component mutiple times in the DOM, see #23123638Clemons
Thanks @SebastienLorber for raising that, I've updated the answer and JSFiddles accordingly.Macnamara
onChange : Event -> unit takes an event, so you can rid yourself of this.refs.searchBox.getDOMNode().value and replace it with evt.target.value for more ideomatic javascript code.Essequibo
@Macnamara thanks for your examples. They still work. Could you, please, explain why having debounce in the separate method is so critical? In this example: jsfiddle.net/TzLZq/11 , the debounced method is called N times, instead of 1.Franklyn
hi @PavelPolyakov, because the debounced function is asynchronous and the synthetic events will be nullified by the time it's executed. That's why e.persist() needs to go in a non-debounced event handler. Your example should look like jsfiddle.net/oza88dye. To learn more, you can check the docs on React's event poolingMacnamara
hi @julen, thanks for the answer. In my case the issue was that _.debounce was created more the 1 time, not once per script. That was the case. Thanks for your examples anyhow!Franklyn
Here is a jsfiddle using a standard javascript debounce method (not the underscore version) - jsfiddle.net/c7a0johaDiscriminating
I would recommend adding {...this.props} to the SearchBox.render(<input {...this.props}> so you can do things like <SearchBox className="foo" ...Wellbalanced
while this is a great answer, I don't recommend using persist especially when there may be lots of events, like on mousemove. I have seen code become totally unresponsive that way. It is much more efficient to extract the needed data from the native event in the event call, and then call the debounced / throttled function with the data only, NOT the event itself. No need to persist the event that wayStavros
This answer is hideously out of date.Bettinabettine
event.target.value is always empty for me when I use event.persist()Vindictive
Note: As of v17, e.persist() doesn’t do anything because the SyntheticEvent is no longer pooled.Pfennig
An answer shouldn't contain "Edit:", "Update", or similar (there is a complete revision history with edit summaries for that information). This answer ought to be as if it was written right now.Cannabis
B
130

2019: Use the 'useCallback' react hook

After trying many different approaches, I found using useCallback to be the simplest and most efficient at solving the multiple calls problem of using debounce within an onChange event.

As per the Hooks API documentation,

useCallback returns a memorized version of the callback that only changes if one of the dependencies has changed.

Passing an empty array as a dependency makes sure the callback is called only once. Here's a simple implementation :

import React, { useCallback } from "react";
import { debounce } from "lodash";

const handler = useCallback(debounce(someFunction, 2000), []);
    
const onChange = (event) => {
    // perform any event related action here
    
    handler();
 };
Beseem answered 28/10, 2019 at 16:3 Comment(10)
Excellent solution if you're using hooks. You saved me many more hours of frustration. Thanks!Waldowaldon
Could you please explain on why the multiple calls happen in the first place? Does debounce() not consider the onChange() callback to be the same callback method?Rosenbaum
I modified this solution to get it to work in my app. First I had to move the line const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []); inside the body of the function component or React outputs an error message about hook use outside of it. Then in the onChange event handler: <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.Rosenbaum
Here is how I used this solution to let user type to an input then send a debounced API call with the input value once he's done typing. #59358592.Rosenbaum
Adding to the above answer ---- const someFunction = (text) => { dispatch({ type: "addText", payload: { id, text, }, }); }; <input type="text" defaultValue={text} onChange={(e) => handler(e.target.value)} />Miniature
This solution finally worked for me. Thanks!Sunken
The simplest one, by far!Biltong
I warn people using useCallback with the debounce from lodash, this will reset all your page state to the initial loaded value. I don't recommend this.Hyperploid
Not sure what the issue was that you had with lodash @FiddleFreak, though one can also use a custom version of debounce function such as presented here: freecodecamp.org/news/javascript-debounce-exampleShellyshelman
eslint will shout at you the following: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. You either have to disable it or use useRef instead.Domineering
C
27

After struggling with the text inputs for a while and not finding a perfect solution on my own, I found this on npm: react-debounce-input.

Here is a simple example:

import React from 'react';
import ReactDOM from 'react-dom';
import {DebounceInput} from 'react-debounce-input';

class App extends React.Component {
state = {
    value: ''
};

render() {
    return (
    <div>
        <DebounceInput
        minLength={2}
        debounceTimeout={300}
        onChange={event => this.setState({value: event.target.value})} />

        <p>Value: {this.state.value}</p>
    </div>
    );
}
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

The DebounceInput component accepts all of the props you can assign to a normal input element. Try it out on CodePen.

Casque answered 1/11, 2017 at 12:10 Comment(2)
After trying many solutions listed here, definitely was the easiest.Brood
This indeed is SO much better solution! Not just because it uses least amount of code, it also allows debouncing class functions (unlike awesome-debounce-promise, which is nearly useless for that reason)Importation
W
22

There can be a simple approach using React hooks.

Step 1: define a state to maintain searched text

const [searchTerm, setSearchTerm] = useState('')

Step 2: Use useEffect to capture any change in searchTerm

useEffect(() => {
  const delayDebounceFn = setTimeout(() => {
    if (searchTerm) {
      // Write your logic here
    }
  }, 400)

  return () => clearTimeout(delayDebounceFn)
}, [searchTerm])

Step 3: Write a function to handle input change

function handleInputChange(value) {
  if (value) {
    setSearchTerm(value)
  }
}

That's all! Call this method as and when required.

Wilkens answered 3/1, 2022 at 7:37 Comment(3)
how do you cancel it if user component was unmounted?Intima
The ``` return () => clearTimeout(delayDebounceFn) ``` Actually clear the timers each time the value is changed in theory because of the array dependency in the use effect @Intima unless I miss understand what useEffect does hereBewitch
Elegant and simple in my opinion this is what I was looking forBewitch
P
21

My solution is hooks-based (written in TypeScript).

I've got two main hooks useDebouncedValue and useDebouncedCallback

First - useDebouncedValue

Let's say we've got a search box, but we want to ask the server for search results after the user has stopped typing for 0.5 seconds:

function SearchInput() {
  const [realTimeValue, setRealTimeValue] = useState('');

  const debouncedValue = useDebouncedValue(realTimeValue, 500); // this value will pick real time value, but will change it's result only when it's seattled for 500ms

  useEffect(() => {
    // this effect will be called on seattled values
    api.fetchSearchResults(debouncedValue);
  }, [debouncedValue])

  return <input onChange={event => setRealTimeValue(event.target.value)} />
}

Implementation

import { useState, useEffect } from "react";

export function useDebouncedValue<T>(input: T, time = 500) {
  const [debouncedValue, setDebouncedValue] = useState(input);

  // Every time the input value has changed - set interval before it's actually committed
  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValue(input);
    }, time);

    return () => {
      clearTimeout(timeout);
    };
  }, [input, time]);

  return debouncedValue;
}

Second useDebouncedCallback

It just creates a 'debounced' function in the scope of your component.

Let's say we've got a component with a button that will show an alert 500 ms after you stopped clicking it.

function AlertButton() {
  function showAlert() {
    alert('Clicking has seattled');
  }

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

  return <button onClick={debouncedShowAlert}>Click</button>
}

Implementation (note I'm using Lodash/debounce as a helper)

import debounce from 'lodash/debounce';
import { useMemo } from 'react';

export function useDebouncedCallback<T extends (...args: any) => any>(callback: T, wait?: number) {
  const debouncedCallback = useMemo(() => debounce(callback, wait), [callback, wait]);

  return debouncedCallback;
}
Perspective answered 10/4, 2020 at 13:1 Comment(4)
love this solution as it needs no new dependenciesZumstein
The downside of the first hook is it will always results in 2 renders of the component that uses it because it uses useState internally. Sometimes that's the behavior you want, but often not.Medullary
Beautiful solution! Thanks for that!Lowbred
Works like a charm and requires no dependencies, amazing!Falsity
D
19

2022 - use a useEffect hook

Your best option at this time is to use the useEffect hook. useEffect lets you set a function that can modify state in response to some async event. Debouncing is asynchronous, so useEffect works nicely for this purpose.

If you return a function from the hook, the returned function will be called before the hook is called again. This lets you cancel the previous timeout, effectively debouncing the function.

Example

Here we have two states, value and tempValue. Setting tempValue will trigger a useEffect hook that will start a 1000 ms timeout which will call a function to copy tempValue into value.

The hook returns a function that unsets the timer. When the hook is called again (i.e., another key is pressed) the timeout is canceled and reset.

const DebounceDemo = () => {
  const [value, setValue] = useState();
  const [tempValue, setTempValue] = useState();

  // This hook will set a 1000 ms timer to copy tempValue into value
  // If the hook is called again, the timer will be cancelled
  // This creates a debounce
  useEffect(
    () => {
      // Wait 1000 ms before copying the value of tempValue into value;
      const timeout = setTimeout(() => {
        setValue(tempValue);
      }, 1000);

      // If the hook is called again, cancel the previous timeout
      // This creates a debounce instead of a delay
      return () => clearTimeout(timeout);
    },
    // Run the hook every time the user makes a keystroke
    [tempValue]
  )

  // Here we create an input to set tempValue.
  // value will be updated 1000 ms after the hook is last called,
  // i.e after the last user keystroke.
  return (
    <>
      <input
        onChange={
          ({ target }) => setTempValue(target.value)
        }
      />
      <p>{ value }</p>
    </>
  )
}
Department answered 18/6, 2022 at 21:0 Comment(3)
Thanks for great answer. There is small typo setValue(tempvalue) -> setValue(tempValue)Underpay
cancelTimeout should be clearTimeoutKenna
This! It is very elegant way to debounce updates with built in functionality. if you have top down approach in your store / state having frequent updates for lists. Thank you!Pursley
C
18

I found this post by Justin Tulk very helpful. After a couple of attempts, in what one would perceive to be the more official way with react/redux, it shows that it fails due to React's synthetic event pooling. His solution then uses some internal state to track the value changed/entered in the input, with a callback right after setState which calls a throttled/debounced redux action that shows some results in realtime.

import React, {Component} from 'react'
import TextField from 'material-ui/TextField'
import { debounce } from 'lodash'

class TableSearch extends Component {

  constructor(props){
    super(props)

    this.state = {
        value: props.value
    }

    this.changeSearch = debounce(this.props.changeSearch, 250)
  }

  handleChange = (e) => {
    const val = e.target.value

    this.setState({ value: val }, () => {
      this.changeSearch(val)
    })
  }

  render() {

    return (
        <TextField
            className = {styles.field}
            onChange = {this.handleChange}
            value = {this.props.value}
        />
    )
  }
}
Cletus answered 14/7, 2017 at 8:3 Comment(1)
nice solution for a state component.Swee
B
17

With debounce you need to keep the original synthetic event around with event.persist(). Here is working example tested with React 16+.

import React, { Component } from 'react';
import debounce from 'lodash/debounce'

class ItemType extends Component {

  evntHandler = debounce((e) => {
    console.log(e)
  }, 500);

  render() {
    return (
      <div className="form-field-wrap"
      onClick={e => {
        e.persist()
        this.evntHandler(e)
      }}>
        ...
      </div>
    );
  }
}
export default ItemType;

With functional component, you can do this -

const Search = ({ getBooks, query }) => {

  const handleOnSubmit = (e) => {
    e.preventDefault();
  }
  const debouncedGetBooks = debounce(query => {
    getBooks(query);
  }, 700);

  const onInputChange = e => {
    debouncedGetBooks(e.target.value)
  }

  return (
    <div className="search-books">
      <Form className="search-books--form" onSubmit={handleOnSubmit}>
        <Form.Group controlId="formBasicEmail">
          <Form.Control type="text" onChange={onInputChange} placeholder="Harry Potter" />
          <Form.Text className="text-muted">
            Search the world's most comprehensive index of full-text books.
          </Form.Text>
        </Form.Group>
        <Button variant="primary" type="submit">
          Search
        </Button>
      </Form>
    </div>
  )
}

References - - https://gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709 - https://blog.revathskumar.com/2016/02/reactjs-using-debounce-in-react-components.html

Baneful answered 11/4, 2019 at 13:48 Comment(1)
Finally a short, consistant but complete example using class component. thx!Bine
M
15

If all you need from the event object is to get the DOM input element, the solution is much simpler – just use ref. Note that this requires Underscore:

class Item extends React.Component {
    constructor(props) {
        super(props);
        this.saveTitle = _.throttle(this.saveTitle.bind(this), 1000);
    }
    saveTitle(){
        let val = this.inputTitle.value;
        // make the ajax call
    }
    render() {
        return <input 
                    ref={ el => this.inputTitle = el } 
                    type="text" 
                    defaultValue={this.props.title} 
                    onChange={this.saveTitle} />
    }
}
Millard answered 9/6, 2016 at 17:0 Comment(1)
defaultValue is what i want! Thank you very mach :)Sparge
M
12

There's a use-debounce package that you can use with ReactJS hooks.

From package's README:

import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

As you can see from the example above, it is set up to update the variable value only once every second (1000 milliseconds).

Monge answered 20/9, 2019 at 4:14 Comment(2)
Still the best choice in January 2021Immobilize
so if i want to fire an event everytime value is set, will i do it like this ? - useEffect(() => { // function here }, [value]);Iapetus
G
9

If you are using Redux, you can do this in a very elegant way with middleware. You can define a Debounce middleware as:

var timeout;
export default store => next => action => {
  const { meta = {} } = action;
  if(meta.debounce){
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      next(action)
    }, meta.debounce)
  }else{
    next(action)
  }
}

You can then add debouncing to action creators, such as:

export default debouncedAction = (payload) => ({
  type : 'DEBOUNCED_ACTION',
  payload : payload,
  meta : {debounce : 300}
}

There's actually already middleware you can get off npm to do this for you.

Gantrisin answered 6/5, 2017 at 1:6 Comment(2)
i think this middleware must be the first one to be executed in applyMiddleware(...) chain if we have manyBourgeois
The timeout isn't initialized and that first clearTimeout will be dealing with undefined for a param. Not good.Neurogram
C
9

Lots of good info here already, but to be succinct. This works for me...

import React, {Component} from 'react';
import _ from 'lodash';

class MyComponent extends Component{
      constructor(props){
        super(props);
        this.handleChange = _.debounce(this.handleChange.bind(this),700);
      }; 
Chilly answered 18/10, 2018 at 15:45 Comment(3)
This doesn't work for me. The state does not update. If I remove _debounce wrapper it works. I love this idea though!Birdman
I'd have to see your code to offer much here, but I suspect there's something else going on... hopefully this much more thorough answer will shed some light. #23123638Chilly
Worked like a charm for me. Wrapped the bound handler function as above, then updated the state in the handler function based on the field input. Thanks!Carpophore
S
8

Using ES6 CLASS, React 15.x.x, and lodash.debounce. I'm using React's refs here since the event loses this bind internally.

class UserInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userInput: ""
    };
    this.updateInput = _.debounce(this.updateInput, 500);
  }


  updateInput(userInput) {
    this.setState({
      userInput
    });
    //OrderActions.updateValue(userInput);//do some server stuff
  }


  render() {
    return ( <div>
      <p> User typed: {
        this.state.userInput
      } </p>
      <input ref = "userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type = "text" / >
      </div>
    );
  }
}

ReactDOM.render( <
  UserInput / > ,
  document.getElementById('root')
);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="root"></div>
Sterilant answered 9/2, 2018 at 9:4 Comment(0)
H
8

You can use the Lodash debounce method. It is simple and effective.

import * as lodash from lodash;

const update = (input) => {
    // Update the input here.
    console.log(`Input ${input}`);
}

const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});

doHandleChange() {
   debounceHandleUpdate(input);
}

You can also cancel the debounce method by using the below method.

this.debounceHandleUpdate.cancel();
Heptahedron answered 6/4, 2018 at 10:45 Comment(0)
C
7

FYI

Here is another PoC implementation:

import React, { useState, useEffect, ChangeEvent } from 'react';

export default function DebouncedSearchBox({
  inputType,
  handleSearch,
  placeholder,
  debounceInterval,
}: {
  inputType?: string;
  handleSearch: (q: string) => void;
  placeholder: string;
  debounceInterval: number;
}) {
  const [query, setQuery] = useState<string>('');
  const [timer, setTimer] = useState<NodeJS.Timer | undefined>();

  useEffect(() => {
    if (timer) {
      clearTimeout(timer);
    }
    setTimer(setTimeout(() => {
      handleSearch(query);
    }, debounceInterval));
  }, [query]);

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
    setQuery(e.target.value);
  };

  return (
    <input
      type={inputType || 'text'}
      className="form-control"
      placeholder={placeholder}
      value={query}
      onChange={handleOnChange}
    />
  );
}
Cognomen answered 25/2, 2019 at 11:55 Comment(1)
This and the ts-hooks variant both fail for me because the debounced function takes stale data if referencing outside state.Mccrae
R
6

There is now another solution for React and React Native in late/2019:

react-debounce-component

<input>
<Debounce ms={500}>
  <List/>
</Debounce>

It's a component, easy to use, tiny and widley supported

Example:

enter image description here

import React from 'react';
import Debounce from 'react-debounce-component';

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {value: 'Hello'}
  }
  render () {
    return (
      <div>
        <input value={this.state.value} onChange={(event) => {this.setState({value: event.target.value})}}/>
        <Debounce ms={1000}>
          <div>{this.state.value}</div>
        </Debounce>
      </div>
    );
  }
}

export default App;

*I'm the creator of this component

Refinement answered 13/11, 2019 at 9:8 Comment(0)
M
6

I can't find any answers under this question mentioning the approach I am using, so I just want to provide an alternative solution here which I think is the best for my use case.

If you are using the popular React hooks toolkit library called react-use, then there is a utility hook called useDebounce() that implemented denounce logic in a quite elegant way.

const [query, setQuery] = useState('');

useDebounce(
  () => {
    emitYourOnDebouncedSearchEvent(query);
  },
  2000,
  [query]
);

return <input onChange={({ currentTarget }) => setQuery(currentTarget.value)} />

For details, please check the library's GitHub page directly.

Mendoza answered 22/12, 2020 at 12:31 Comment(0)
Q
6

As of June 2021, you can simply implement xnimorz's solution: use-debounce

import { useState, useEffect, useRef } from "react";
// Usage
function App() {
  // State and setters for ...
  // Search term
  const [searchTerm, setSearchTerm] = useState("");
  // API search results
  const [results, setResults] = useState([]);
  // Searching status (whether there is pending API request)
  const [isSearching, setIsSearching] = useState(false);
  // Debounce search term so that it only gives us latest value ...
  // ... if searchTerm has not been updated within last 500 ms.
  // The goal is to only have the API call fire when user stops typing ...
  // ... so that we aren't hitting our API rapidly.
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  // Effect for API call
  useEffect(
    () => {
      if (debouncedSearchTerm) {
        setIsSearching(true);
        searchCharacters(debouncedSearchTerm).then((results) => {
          setIsSearching(false);
          setResults(results);
        });
      } else {
        setResults([]);
        setIsSearching(false);
      }
    },
    [debouncedSearchTerm] // Only call effect if debounced search term changes
  );
  return (
    <div>
      <input
        placeholder="Search Marvel Comics"
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {isSearching && <div>Searching ...</div>}
      {results.map((result) => (
        <div key={result.id}>
          <h4>{result.title}</h4>
          <img
            src={`${result.thumbnail.path}/portrait_incredible.${result.thumbnail.extension}`}
          />
        </div>
      ))}
    </div>
  );
}
// API search function
function searchCharacters(search) {
  const apiKey = "f9dfb1e8d466d36c27850bedd2047687";
  return fetch(
    `https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`,
    {
      method: "GET",
    }
  )
    .then((r) => r.json())
    .then((r) => r.data.results)
    .catch((error) => {
      console.error(error);
      return [];
    });
}
// Hook
function useDebounce(value, delay) {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(
    () => {
      // Update debounced value after delay
      const handler = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);
      // Cancel the timeout if value changes (also on delay change or unmount)
      // This is how we prevent debounced value from updating if value is changed ...
      // .. within the delay period. Timeout gets cleared and restarted.
      return () => {
        clearTimeout(handler);
      };
    },
    [value, delay] // Only recall effect if value or delay changes
  );
  return debouncedValue;
}
Quidnunc answered 3/6, 2021 at 6:16 Comment(0)
P
5

A nice and clean solution, that doesn't require any external dependencies:

Debouncing with React Hooks

It uses a custom plus the useEffect React hooks and the setTimeout / clearTimeout method.

Pluperfect answered 29/7, 2019 at 17:40 Comment(0)
S
4

Just another variant with recent React and Lodash.

class Filter extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired
  }

  state = {
    initialText: '',
    text: ''
  }

  constructor (props) {
    super(props)

    this.setText = this.setText.bind(this)
    this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
  }

  static getDerivedStateFromProps (nextProps, prevState) {
    const { text } = nextProps

    if (text !== prevState.initialText) {
      return { initialText: text, text }
    }

    return null
  }

  setText (text) {
    this.setState({ text })
    this.onChange(text)
  }

  onChange (text) {
    this.props.onChange(text)
  }

  render () {
    return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
  }
}
Synonymy answered 15/1, 2019 at 22:18 Comment(0)
C
3

Instead of wrapping the handleOnChange in a debounce(), wrap the Ajax call inside the callback function inside the debounce, thereby not destroying the event object.

So something like this:

handleOnChange: function (event) {
   debounce(
     $.ajax({})
  , 250);
}
Confectionary answered 28/6, 2014 at 3:58 Comment(1)
Because the event object is not immutable and is destroyed by ReactJS, so even if you wrap and attain a closure capture, the code will fail.Essequibo
T
3

Try:

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) {
    debounce(\\ Your handleChange code , 200);
  }
});
Theorize answered 24/2, 2020 at 16:31 Comment(2)
Is this at all different from the original poster's solution?Ress
Yes, it's different here: debounce(handleChange , 200);Theorize
N
2

Here is an example I came up with that wraps another class with a debouncer. This lends itself nicely to being made into a decorator/higher order function:

export class DebouncedThingy extends React.Component {
    static ToDebounce = ['someProp', 'someProp2'];
    constructor(props) {
        super(props);
        this.state = {};
    }
    // On prop maybe changed
    componentWillReceiveProps = (nextProps) => {
        this.debouncedSetState();
    };
    // Before initial render
    componentWillMount = () => {
        // Set state then debounce it from here on out (consider using _.throttle)
        this.debouncedSetState();
        this.debouncedSetState = _.debounce(this.debouncedSetState, 300);
    };
    debouncedSetState = () => {
        this.setState(_.pick(this.props, DebouncedThingy.ToDebounce));
    };
    render() {
        const restOfProps = _.omit(this.props, DebouncedThingy.ToDebounce);
        return <Thingy {...restOfProps} {...this.state} />
    }
}
Neomineomycin answered 16/1, 2016 at 22:46 Comment(0)
B
2

Here's a snippet using @Abra's approach wrapped in a function component (we use fabric for the UI, just replace it with a simple button)

import React, { useCallback } from "react";
import { debounce } from "lodash";

import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';

const debounceTimeInMS = 2000;

export const PrimaryButtonDebounced = (props) => {

    const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });

    const clickHandlerDebounced = useCallback((e, value) => {

        debouncedOnClick(e, value);

    },[]);

    const onClick = (e, value) => {

        clickHandlerDebounced(e, value);
    };

    return (
        <PrimaryButton {...props}
            onClick={onClick}
        />
    );
}
Blancheblanchette answered 20/11, 2019 at 9:8 Comment(0)
B
2

I met this problem today and solved it using setTimeout and clearTimeout.

I will give an example that you could adapt:

import React, { Component } from 'react'

const DEBOUNCE_TIME = 500

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  onChangeHandler = (event) => {
    // Clear the last registered timer for the function
    clearTimeout(this.debounceTimer);

    // Set a new timer
    this.debounceTimer = setTimeout(
      // Bind the callback function to pass the current input value as the argument
      this.getSuggestions.bind(null, event.target.value),
      DEBOUNCE_TIME
    )
  }

  // The function that is being debounced
  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <input type="text" onChange={this.onChangeHandler} />
    )
  }
}

export default PlacesAutocomplete

You could also refactor it in its own function component:

import React from 'react'

function DebouncedInput({ debounceTime, callback}) {
  let debounceTimer = null
  return (
    <input type="text" onChange={(event) => {
      clearTimeout(debounceTimer);

      debounceTimer = setTimeout(
        callback.bind(null, event.target.value),
        debounceTime
      )
    }} />
  )
}

export default DebouncedInput

And use it like:

import React, { Component } from 'react'
import DebouncedInput from '../DebouncedInput';

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <DebouncedInput debounceTime={500} callback={this.getSuggestions} />
    )
  }
}

export default PlacesAutocomplete
Brumby answered 22/2, 2020 at 16:53 Comment(0)
W
2

This solution does not need any extra libraries, and it also fires things up when the user presses Enter:

const debounce = (fn, delay) => {
    let timer = null;
    return function() {
        const context = this,
        args = arguments;
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, delay);
    };
}

const [search, setSearch] = useState('');
const [searchFor, setSearchFor] = useState(search);

useEffect(() => {
    console.log("Search:", searchFor);
}, [searchFor]);

const fireChange = event => {
    const { keyCode } = event;
    if (keyCode === 13) {
        event.preventDefault();
        setSearchFor(search);
    }
}

const changeSearch = event => {
    const { value } = event.target;
    setSearch(value);
    debounceSetSearchFor(value);
};

const debounceSetSearchFor = useCallback(debounce(function(value) {
    setSearchFor(value);
}, 250), []);

And the input could be like:

<input value={search} onKeyDown={fireChange} onChange={changeSearch}  />
Wartburg answered 1/3, 2020 at 19:58 Comment(1)
Pureeeeeeeee JS, Love itLauderdale
H
2

Hook:

import {useState} from "react";

const useDebounce = ({defaultTimeout = 250, defaultIdentifier = 'default'} = {}) => {

    const [identifiers, setIdentifiers] = useState({[defaultIdentifier]: null});

    return ({fn = null, identifier = defaultIdentifier, timeout = defaultTimeout} = {}) => {
        if (identifiers.hasOwnProperty(identifier)) clearTimeout(identifiers[identifier]);
        setIdentifiers({...identifiers, [identifier]: setTimeout(fn, timeout)});
    };
};

export default useDebounce;

And use it anywhere (in same file use identifier to prevent concurrence) like:

const debounce = useDebounce();

const handlerA = () => {
    debounce({fn: () => console.log('after 2000ms of last call with identifier A'), identifier: 'A', timeout: 2000});
};

const handlerB = () => {
    debounce({fn: () => console.log('after 1500ms of last call with identifier B'), identifier: 'B', timeout: 1500});
};
Hummer answered 15/12, 2020 at 18:36 Comment(1)
looks nice, if i understand right a use-case can look like the following snippet: const debounce = useDebounce(); const debouncedSearchInputHandler = (event) => { setSearchInput(event.target.value); debounce({fn: () => startRestCall(event.target.value), timeout: 1000}); };Ninos
B
2

Simple and effective: Use use-debounce

import { useDebouncedCallback } from 'use-debounce';

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  const debounced = useDebouncedCallback(
    (value) => {
      setValue(value);
    },
    // Delay
    1000
  );

  return (
    <div>
      <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
      <p>Debounced value: {value}</p>
    </div>
  );
}
Bearish answered 10/5, 2021 at 4:56 Comment(2)
Please add comments for the downvoting, I have been using this code in my current app and its perfectly workingBearish
This only sends the last letter enteredOverthecounter
A
1

I was searching for a solution to the same problem and came across this thread as well as some others but they had the same problem: if you are trying to do a handleOnChange function and you need the value from an event target, you will get cannot read property value of null or some such error. In my case, I also needed to preserve the context of this inside the debounced function since I'm executing a fluxible action. Here's my solution, it works well for my use case so I'm leaving it here in case anyone comes across this thread:

// at top of file:
var myAction = require('../actions/someAction');

// inside React.createClass({...});

handleOnChange: function (event) {
    var value = event.target.value;
    var doAction = _.curry(this.context.executeAction, 2);

    // only one parameter gets passed into the curried function,
    // so the function passed as the first parameter to _.curry()
    // will not be executed until the second parameter is passed
    // which happens in the next function that is wrapped in _.debounce()
    debouncedOnChange(doAction(myAction), value);
},

debouncedOnChange: _.debounce(function(action, value) {
    action(value);
}, 300)
Athirst answered 30/5, 2015 at 14:21 Comment(2)
What is a "fluxible action"?Cannabis
OK, the OP has left the building: "Last seen more than 8 years ago"Cannabis
U
1

For throttle or debounce, the best way is to create a function creator, so you can use it anywhere, for example:

  updateUserProfileField(fieldName) {
    const handler = throttle(value => {
      console.log(fieldName, value);
    }, 400);
    return evt => handler(evt.target.value.trim());
  }

And in your render method you can do:

<input onChange={this.updateUserProfileField("givenName").bind(this)}/>

The updateUserProfileField method will create a separated function each time you call it.

Note: Don't try to return the handler directly. For example, this will not work:

 updateUserProfileField(fieldName) {
    return evt => throttle(value => {
      console.log(fieldName, value);
    }, 400)(evt.target.value.trim());
  }

The reason why this will not work is because this will generate a new throttle function each time the event called instead of using the same throttle function, so basically the throttle will be useless ;)

Also, if you use debounce or throttle, you don't need setTimeout or clearTimeout. This is actually why we use them :P

Underclothes answered 4/7, 2017 at 9:39 Comment(0)
M
1

React Ajax debounce and cancellation example solution using React hooks and reactive programming (RxJS):

import React, { useEffect, useState } from "react";
import { ajax } from "rxjs/ajax";
import { debounceTime, delay, takeUntil } from "rxjs/operators";
import { Subject } from "rxjs/internal/Subject";

const App = () => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filterChangedSubject] = useState(() => {
    // Arrow function is used to init Singleton Subject (in a scope of a current component)
    return new Subject<string>();
  });

  useEffect(() => {
    // Effect that will be initialized once on a React component init.
    const subscription = filterChangedSubject
      .pipe(debounceTime(200))
      .subscribe((filter) => {
        if (!filter) {
          setLoading(false);
          setItems([]);
          return;
        }
        ajax(`https://swapi.dev/api/people?search=${filter}`)
          .pipe(
            // Current running Ajax is cancelled on filter change.
            takeUntil(filterChangedSubject)
          )
          .subscribe(
            (results) => {
              // Set items will cause render:
              setItems(results.response.results);
            },
            () => {
              setLoading(false);
            },
            () => {
              setLoading(false);
            }
          );
      });

    return () => {
      // On Component destroy. notify takeUntil to unsubscribe from current running Ajax request
      filterChangedSubject.next("");
      // Unsubscribe filter change listener
      subscription.unsubscribe();
    };
  }, []);

  const onFilterChange = (e) => {
    // Notify subject about the filter change
    filterChangedSubject.next(e.target.value);
  };
  return (
    <div>
      Cards
      {loading && <div>Loading...</div>}
      <input onChange={onFilterChange}></input>
      {items && items.map((item, index) => <div key={index}>{item.name}</div>)}
    </div>
  );
};

export default App;
Martelle answered 30/7, 2020 at 21:19 Comment(0)
D
1

If you just need to perform a debounce in a button for requesting data, the code provided might be helpful to you:

  1. Create a function to prevent the default with conditional statement if requesting is true or false

  2. Implement the useState Hook and useEffect Hook

    const PageOne = () => {
     const [requesting, setRequesting] = useState(false);
    
      useEffect(() => {
        return () => {
          setRequesting(false);
        };
      }, [requesting]);
    
      const onDebounce = (e) => {
        if (requesting === true) {
          e.preventDefault();
        }
        // ACTIONS
        setLoading(true);
      };
    
     return (
      <div>
    
        <button onClick={onDebounce}>Requesting data</button>
      </div>
     )
    }
    
Duplex answered 17/11, 2020 at 15:45 Comment(0)
G
1
/**
 * Returns a function with the same signature of input `callback` (but without an output) that if called, smartly
 * executes the `callback` in a debounced way.<br>
 * There is no `delay` (to execute the `callback`) in the self-delayed tries (try = calling debounced callback). It
 * will defer **only** subsequent tries (that are earlier than a minimum timeout (`delay` ms) after the latest
 * execution). It also **cancels stale tries** (that have been obsoleted because of creation of newer tries during the
 * same timeout).<br>
 * The timeout won't be expanded! So **the subsequent execution won't be deferred more than `delay`**, at all.
 * @param {Function} callback
 * @param {number} [delay=167] Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
 * @return {Function}
 */
export function smartDebounce (callback, delay = 167) {
  let minNextExecTime = 0
  let timeoutId

  function debounced (...args) {
    const now = new Date().getTime()
    if (now > minNextExecTime) { // execute immediately
      minNextExecTime = now + delay // there would be at least `delay` ms between ...
      callback.apply(this, args) // ... two consecutive executions
      return
    }
    // schedule the execution:
    clearTimeout(timeoutId) // unset possible previous scheduling
    timeoutId = setTimeout( // set new scheduling
      () => {
        minNextExecTime = now + delay // there would be at least `delay` ms between ...
        callback.apply(this, args) // ... two consecutive executions
      },
      minNextExecTime - now, // 0 <= timeout <= `delay` ... (`minNextExecTime` <= `now` + `delay`)
    )
  }

  debounced.clear = clearTimeout.bind(null, timeoutId)

  return debounced
}
/**
 * Like React's `useCallback`, but will {@link smartDebounce smartly debounce} future executions.
 * @param {Function} callback
 * @param {[]} deps
 * @param {number} [delay=167] - Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
 */
export const useDebounced = (callback, deps, delay = 167) =>
  useMemo(() => smartDebounce(callback, delay), [...deps, delay])
Gavrielle answered 9/4, 2022 at 1:22 Comment(0)
D
0

Julen's solution is kind of hard to read. Here's clearer and to-the-point react code for anyone who stumbled upon that answer based on the title and not the tiny details of the question.

tl;dr version: when you would update to observers send call a schedule method instead and that in turn will actually notify the observers (or perform Ajax, etc.)

Complete jsfiddle with example component jsfiddle

var InputField = React.createClass({

    getDefaultProps: function () {
        return {
            initialValue: '',
            onChange: null
        };
    },

    getInitialState: function () {
        return {
            value: this.props.initialValue
        };
    },

    render: function () {
        var state = this.state;
        return (
            <input type="text"
                   value={state.value}
                   onChange={this.onVolatileChange} />
        );
    },

    onVolatileChange: function (event) {
        this.setState({ 
            value: event.target.value 
        });

        this.scheduleChange();
    },

    scheduleChange: _.debounce(function () {
        this.onChange();
    }, 250),

    onChange: function () {
        var props = this.props;
        if (props.onChange != null) {
            props.onChange.call(this, this.state.value)
        }
    },

});
Draper answered 12/9, 2014 at 11:47 Comment(4)
Won't this make the state/timing of the debounce global across all instances of InputField, because it's created with the class definition? Maybe that's what you want, but it's worth noting regardless.Proviso
dangerous if mounted multiple time in the dom, check #23123638Clemons
This is a bad solution, because of double-mount issues -- you're making your function to scheduleChange a singleton and that's not a good idea. -1Essequibo
What do you mean by "update to observers send call a schedule method instead" (seems incomprehensible)? Please respond by editing (changing) your answer, not here in comments (but *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** without *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** "Edit:", "Update:", or similar - the answer should appear as if it was written today).Cannabis
T
0

Here's a working TypeScript example for those who use TS and want to debounce async functions.

function debounce<T extends (...args: any[]) => any>(time: number, func: T): (...funcArgs: Parameters<T>) => Promise<ReturnType<T>> {
     let timeout: Timeout;

     return (...args: Parameters<T>): Promise<ReturnType<T>> => new Promise((resolve) => {
         clearTimeout(timeout);
         timeout = setTimeout(() => {
             resolve(func(...args));
         }, time)
     });
 }
Tommyetommyrot answered 17/2, 2019 at 17:23 Comment(0)
T
0

Create this class (it’s written in TypeScript, but it’s easy to convert it to JavaScript).

export class debouncedMethod<T> {
  constructor(method: T, debounceTime: number) {
    this._method = method;
    this._debounceTime = debounceTime;
  }
  private _method: T;
  private _timeout: number;
  private _debounceTime: number;
  public invoke: T = ((...args: any[]) => {
    this._timeout && window.clearTimeout(this._timeout);
    this._timeout = window.setTimeout(() => {
      (this._method as any)(...args);
    }, this._debounceTime);
  }) as any;
}

And to use

var foo = new debouncedMethod((name, age) => {
 console.log(name, age);
}, 500);
foo.invoke("john", 31);
Thomas answered 18/5, 2019 at 12:40 Comment(0)
M
0

Avoid using event.persist() - you want to let React recycle the synthetic event. I think the cleanest way whether you use classes or hooks is to split the callback into two pieces:

  1. The callback with no debouncing
  2. Calls a debounced function with only the pieces of the event you need (so the synthetic event can be recycled)

Classes

handleMouseOver = throttle(target => {
  console.log(target);
}, 1000);

onMouseOver = e => {
  this.handleMouseOver(e.target);
};

<div onMouseOver={this.onMouseOver} />

Functions

const handleMouseOver = useRef(throttle(target => {
  console.log(target);
}, 1000));

function onMouseOver(e) {
  handleMouseOver.current(e.target);
}

<div onMouseOver={this.onMouseOver} />

Note that if your handleMouseOver function uses state from within the component, you should use useMemo instead of useRef and pass those as dependencies otherwise you will be working with stale data (does not apply to classes of course).

Manofwar answered 3/9, 2019 at 9:20 Comment(0)
H
0

Extend useState hook

import { useState } from "react";
import _ from "underscore"
export const useDebouncedState = (initialState, durationInMs = 500) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedFunction = _.debounce(setInternalState, durationInMs);
    return [internalState, debouncedFunction];
};
export default useDebouncedState;

Use hook

import useDebouncedState from "../hooks/useDebouncedState"
//...
const [usernameFilter, setUsernameFilter] = useDebouncedState("")
//...
<input id="username" type="text" onChange={e => setUsernameFilter(e.target.value)}></input>

https://trippingoncode.com/react-debounce-hook/

Hurlow answered 11/2, 2020 at 22:2 Comment(0)
C
0

We would need to pass the setter to the debounced method:

Here's a snippet from an example on StackBlitz:

import React from "react";
import debounce from "lodash/debounce";

export default function App() {
  const [state, setState] = React.useState({
    debouncedLog: ""
  });

  const debouncedLog = React.useCallback(
    debounce((setState, log) => {
      setState(prevState => ({
        ...prevState,
        debouncedLog: log
      }));
    }, 500),
    []
  );

  const onChange = React.useCallback(({ target: { value: log } }) => {
    debouncedLog(setState, log); // passing the setState along...
  }, []);
  return (
    <div>
      <input onChange={onChange} style={{ outline: "1px blue solid" }} />

      <pre>Debounced Value: {state.debouncedLog}</pre>
    </div>
  );
}

Good Luck...

Concerning answered 2/5, 2021 at 23:24 Comment(0)
E
0

You have to use useCallback as mentioned in the blog post How to use debounce and throttle in React and abstract them into hooks.

import React, { useCallback } from 'react';
import debounce from 'debounce'; // Or another package

function App() {
    ...
    const debouncedSave = useCallback(
        debounce(x => foo(x), 1000),
        [], // Will be created only once initially
    );
    ...
}
Excellence answered 27/5, 2021 at 19:25 Comment(0)
W
0

You can use a reference variable to store the timer and then clear it out. Below is an example of implementing debouncing in react without the use of any third-party package

import { useState, useRef } from "react";
import "./styles.css";

export default function App() {
  // Variables for debouncing
  const [text, setText] = useState("");
  const timer = useRef();

  // Variables for throtteling
  const [throttle, setThrottle] = useState(false)


  const handleDebouncing = ({ target }) => {
    clearTimeout(timer.current)

    timer.current = setTimeout(() => {
      callApi();
    }, 300);

    setText(target.value);
  };

  const handleThrottleing = () => {
    callApi()

    setThrottle(true)

    setTimeout(() => {
      setThrottle(false)
    }, 2000)
  }

  const callApi = () => {
    console.log("Calling Api");
  };

  return (
    <div className="App">
      <input type="text" onChange={handleDebouncing} />

      <button onClick={handleThrottleing} disabled={throttle} >Click me to see throtteling</button>
    </div>
  );
}

Windlass answered 17/3, 2022 at 8:52 Comment(0)
M
0

This is the cleanest way I have found to accomplish debouncing. I know there are similar answers, but I would like to document the bare idea, so anyone in the future can grasp and fit in their code.

let timer = null;
function debouce(func) {
  clearTimeout(timer);
  timer = setTimeout(() => {
    func();
  }, 1000);
}

// simulate frequent calls
debouce(()=> console.log("call 1"));
debouce(()=> console.log("call 2"));
debouce(()=> console.log("call 3"));

// simulate future call
setTimeout(() => {
  debouce(()=> console.log("call 4"));
}, 2000);
Madelle answered 28/4, 2023 at 8:14 Comment(0)
F
-1

You can also use a self-written mixin, something like this:

var DebounceMixin = {
  debounce: function(func, time, immediate) {
    var timeout = this.debouncedTimeout;
    if (!timeout) {
      if (immediate) func();
      this.debouncedTimeout = setTimeout(function() {
        if (!immediate) func();
        this.debouncedTimeout = void 0;
      }.bind(this), time);
    }
  }
};

And then use it in your component like this:

var MyComponent = React.createClass({
  mixins: [DebounceMixin],
  handleClick: function(e) {
    this.debounce(function() {
      this.setState({
        buttonClicked: true
      });
    }.bind(this), 500, true);
  },
  render: function() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
});
Frustum answered 15/1, 2015 at 11:16 Comment(3)
That's not debounce, it's 'delay'. Debounce resets the timeout every event that happens before the timeout. -1Essequibo
@Essequibo My bad, you are right. By the way, it's easy to make debounce like this.Frustum
this solution does not answer the question, as it would trigger the action exactly after the specified timeout. But in this topic the timeout should be "extendable" if the debounce is called multiple times within the timeout.Panatella
W
-1

You can use tlence:

function log(server) {
  console.log('connecting to', server);
}

const debounceLog = debounce(log, 5000);
// just run last call to 5s
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
Wise answered 26/1, 2019 at 14:38 Comment(0)
B
-1

If you don't like to add Lodash or any other package:

import React, { useState, useRef } from "react";

function DebouncedInput() {
  const [isRefetching, setIsRefetching] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  const previousSearchTermRef = useRef("");

  function setDebouncedSearchTerm(value) {
    setIsRefetching(true);
    setSearchTerm(value);
    previousSearchTermRef.current = value;
    setTimeout(async () => {
      if (previousSearchTermRef.current === value) {
        try {
          // await refetch();
        } finally {
          setIsRefetching(false);
        }
      }
    }, 500);
  }

  return (
    <input
      value={searchTerm}
      onChange={(event) => setDebouncedSearchTerm(event.target.value)}
    />
  );
}
Bentinck answered 16/4, 2020 at 10:23 Comment(0)
K
-1

If we are interested in using epic.js with React, we could use the debounceTime RxJS/operator with the epic.js library. Hence instead of using callbacks, we could use observables in React with the help of epic.js.

Keister answered 18/5, 2021 at 12:1 Comment(0)
P
-2
class UserListComponent extends Component {
    constructor(props) {
        super(props);
        this.searchHandler = this.keyUpHandler.bind(this);
        this.getData = this.getData.bind(this);
        this.magicSearch = this.magicSearch.bind(this,500);
    }
    getData = (event) => {
        console.log(event.target.value);
    }
    magicSearch = function (fn, d) {
        let timer;
        return function () {
            let context = this;
            clearTimeout(timer);
            timer = setTimeout(() => {
                fn.apply(context, arguments)
            }, d);
        }
    }
    keyUpHandler = this.magicSearch(this.getData, 500);
    render() {
        return (
             <input type="text" placeholder="Search" onKeyUp={this.searchHandler} />
        )
    }
}
Paterfamilias answered 1/12, 2020 at 2:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.