Search code examples
javascriptreactjsreact-hookslodashdebouncing

lodash debouce isn't debouncing function is called many times insead of just once, react hooks react native


I'm doing a search for multiple objects in an array onChange of the text Input so we need a debouncing function to prevent any unnecessary API calls to the search function

The problem:

the debounce funtion waits for the given amount of time but it calls the API function multiple times not only once

expected:

only one API call when the debounce waiting time is finished

code:

const [input, setInput] = useState('');
  const [searchResults, setSearchResults] = useState([]);

  const mockApiCall = async (result, waitingTime = 2000) => {
    await new Promise((resolve) => setTimeout(resolve, waitingTime));

    console.log('making search request', result);
    console.log('post');

    return setSearchResults((prev) => [...prev, result]);
  };

  useEffect(() => {
    console.log('pre');
     const search = _.debounce(mockApiCall, 4000);
    search(input);
    // return () => {
    //   _.debounce.cancel();
    // };
  }, [input]);

<View style={styles.container}>
      <TextInput
        style={{
          height: 40,
          borderColor: 'gray',
          borderWidth: 1,
          placeholderTextColor: 'gray',
        }}
        onChangeText={(text) => setInput(text)}
        value={input}
      />
      {searchResults.map((ele) => (
        <Text>{ele}</Text>
      ))}
    </View>

snack example :

https://snack.expo.io/@mansouriala/search-using-debounce-rn


Solution

  • When you wrap a function in debounce, you get back a new function with an internal timer (the debounced function). You should memoize the returned function, and use it to call the api (snack).

    Whenever a debounced function is invoked, it's internal timer resets, and is starts to count the timeout (4000ms in your case) again. If you recreate the function, the previous debounce function doesn't reset it's timer (it's not called again), and it invokes the wrapped function.

    const mockApiCall = useMemo(() => _.debounce(async(result, waitingTime = 2000) => {
      await new Promise((resolve) => setTimeout(resolve, waitingTime));
    
      console.log('making search request', result);
      console.log('post');
    
      return setSearchResults((prev) => [...prev, result]);
    }, 4000), []);
    
    useEffect(() => {
      console.log('pre');
    
      mockApiCall(input); // call the debounced function
      
      return () => {
        mockApiCall.cancel(); // cancel the debounced function
      };
    }, [mockApiCall, input]);