39

I'm playing around with React Native and lodash's debounce.

Using the following code only make it work like a delay and not a debounce.

<Input
 onChangeText={(text) => {
  _.debounce(()=> console.log("debouncing"), 2000)()
 }
/>

I want the console to log debounce only once if I enter an input like "foo". Right now it logs "debounce" 3 times.

Norfeldt
  • 8,272
  • 23
  • 96
  • 152
  • `onChangeText` is invoked every time you change input, typing foo will make debounce function to invoke 3 times, so the behavior is correct. – jmac Dec 18 '16 at 17:21
  • @jmac I'm pretty sure the fault is always on me but how do it archive want I want so it only fires once and after firering it can be fired again – Norfeldt Dec 18 '16 at 17:46
  • 5
    _.debounce creates a new function, which should be passed directly as a callback, but you've wrapped it in extra function and manually create+call a new instance of debounced function every time change happens. You should eliminate this extra wrapper: `onChangeText={_.debounce((text) => console.log('debouncing', text), 2000)}`. Don't worry about the arguments, it will pass them down to your handler. – Michael Radionov Dec 18 '16 at 17:54

5 Answers5

82

Debounce function should be defined somewhere outside of render method since it has to refer to the same instance of the function every time you call it as oppose to creating a new instance like it's happening now when you put it in the onChangeText handler function.

The most common place to define a debounce function is right on the component's object. Here's an example:

class MyComponent extends React.Component {
  constructor() {
    this.onChangeTextDelayed = _.debounce(this.onChangeText, 2000);
  }

  onChangeText(text) {
    console.log("debouncing");
  }

  render() {
    return <Input onChangeText={this.onChangeTextDelayed} />
  }
}
George Borunov
  • 1,562
  • 1
  • 15
  • 14
  • 2
    If you have any reason to immediately invoke the debounced function (when the onBlur event happens, for example), this is ideally suited. Just call `this.onChangeTextDelayed.flush()` and the last timeout will be immediately skipped. – arvidkahl May 08 '18 at 10:50
  • debounce less than 100ms crashes my application. Anyone else have that issue? – Frank Goortani May 07 '19 at 23:49
51

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.

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();
 };

Hope this helps!

Sameer Ingavale
  • 2,120
  • 1
  • 11
  • 9
6

Updated 2021

As other answers already stated, the debounce function reference must be created once and by calling the same reference to denounce the relevant function (i.e. changeTextDebounced in my example).

First things first import

import {debounce} from 'lodash';

For Class Component

class SomeClassComponent extends React.Component {
  componentDidMount = () => {
    this.changeTextDebouncer = debounce(this.changeTextDebounced, 500);
  }

  changeTextDebounced = (text) => {
    console.log("debounced");
  }

  render = () => {
    return <Input onChangeText={this.changeTextDebouncer} />;
  }
}

For Functional Component

const SomeFnComponent = () => {
  const changeTextDebouncer = useCallback(debounce(changeTextDebounced, 500), []);

  const changeTextDebounced = (text) => {
    console.log("debounced");
  }

  return <Input onChangeText={changeTextDebouncer} />;
}
rule_it_subir
  • 650
  • 10
  • 12
0

so i came across the same problem for textInput where my regex was being called too many times did below to avoid

  const emailReg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w\w+)+$/;

  const debounceReg = useCallback(debounce((text: string) => {
    if (emailReg.test(text)) {
      setIsValidEmail(true);
    } else {
      setIsValidEmail(false);
    }
  }, 800), []);

  const onChangeHandler = (text: string) => {
    setEmailAddress(text);
    debounceReg(text)
  };

and my debounce code in utils is

function debounce<Params extends any[]>(
    f: (...args: Params) => any,
    delay: number,
): (...args: Params) => void {
    let timer: NodeJS.Timeout;

    return (...args: Params) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            f(...args);
        }, delay);
    };
}
Waqas Ali
  • 91
  • 3
0

Working solution for functional component:

  import {debounce} from 'lodash';

  const [text, settext] = useState('');

  <TextInput
          style={styles.textInput}
          numberOfLines={1}
          placeholder={placeholder}
          placeholderTextColor={COLOR_GRAY_510}
          onChangeText={handleChange}
          value={text}
        />

----------------------------------------------
  const handleChange = async textParam => {
      settext(textParam);
      debounceCall(textParam);
  };

  const debounceCall = useCallback(
    debounce(textParam => {
      searchAPICall(textParam);
    }, 500),
    [],
  );

  const searchAPICall = async textParam => {
    if (textParam.length > 2) {
      let suggestions = await suggestAddressList(textParam);
      setresultList(suggestions.length > 0 ? suggestions : []);
    } else {
      setresultList([]);
    }
  };
Ajmal Hasan
  • 885
  • 11
  • 9