Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with debounce in useEffect

I have a form with username input and I am trying to verify if the username is in use or not in a debounce function. The issue I'm having is that my debounce doesn't seem to be working as when I type "user" my console looks like

u us use user 

Here is my debounce function

export function debounce(func, wait, immediate) {     var timeout;      return () => {         var context = this, args = arguments;          var later = () => {             timeout = null;             if (!immediate) func.apply(context, args);         };          var callNow = immediate && !timeout;          clearTimeout(timeout);          timeout = setTimeout(later, wait);          if (callNow) func.apply(context, args);     }; }; 

And here is how I'm calling it in my React component

import React, { useEffect } from 'react'   // verify username useEffect(() => {     if(state.username !== "") {         verify();     } }, [state.username])  const verify = debounce(() => {     console.log(state.username) }, 1000); 

The debounce function seems to be correct? Is there a problem with how I am calling it in react?

like image 437
Ryne Avatar asked May 13 '20 22:05

Ryne


People also ask

How do you use debounce in React JS?

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

What is the difference between Debounce and throttle?

Throttle: the original function will be called at most once per specified period. Debounce: the original function will be called after the caller stops calling the decorated function after a specified period.

Does useEffect runs after every render?

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.


1 Answers

Every time your component re-render, a new debounced verify function is created, which means that inside useEffect you are actually calling different functions which defeats the purpose of debouncing.

It's like if you were doing something like this:

const debounced1 = debounce(() => { console.log(state.username) }, 1000); debounced1();  const debounced2 = debounce(() => { console.log(state.username) }, 1000); debounced2();  const debounced3 = debounce(() => { console.log(state.username) }, 1000); debounced3(); 

as opposed to what you really want:

const debounced = debounce(() => { console.log(state.username) }, 1000); debounced(); debounced(); debounced(); 

One way to solve this is to use useCallback which will always return the same callback (when you pass in an empty array as a second argument), also, I would pass the username to this function instead of accessing the state inside (otherwise you will be accessing a stale state):

import { useCallback } from "react"; const App => () {   const [username, setUsername] = useState("");    useEffect(() => {     if (username !== "") {       verify(username);     }   }, [username]);    const verify = useCallback(     debounce(name => {       console.log(name);     }, 200),     []   );    return <input onChange={e => setUsername(e.target.value)} />; } 

Also you need to slightly update your debounce function since it's not passing arguments correctly to the debounced function.

function debounce(func, wait, immediate) {   var timeout;    return (...args) => { <--- needs to use this `args` instead of the ones belonging to the enclosing scope     var context = this; ... 

demo

like image 119
Hamza El Aoutar Avatar answered Sep 28 '22 04:09

Hamza El Aoutar