How to use debounce with React

I’ve seen quite a few similar questions here, but none of the suggested solutions seemed to be working for me. Here’s my problem (please notice that I’m very new to React, just learning it):

I have the following code in my App.js:

function App() {
  const [movieSearchString, setMovieSearchString] = useState('')
  
  const providerValue = {
    moviesList: moviesList,
    movieSearchString: movieSearchString,
  }
  
  const searchMovieHandler = () => {
    console.log('movie handler called')
    const params = {
      apikey: 'somekey'
    }
    
    if (movieSearchString.length > 2) {
      params.search = movieSearchString
    }
    
    debounce(() => {
      console.log('deb: ' + movieSearchString)
    }, 1000)
  }
  
  const movieInputChangeHandler = string => {
    console.log('onMovieInputChange', string);
    setMovieSearchString(string)
    searchMovieHandler()
  }
  
  return (
    <MoviesContext.Provider value={providerValue}>
      <div className="App d-flex flex-column px-4 py-2">
        <SearchBar
          onMovieInputChange={movieInputChangeHandler}
        />
        ...Rest of the content
      </div>
    </MoviesContext.Provider>
  );
}

In this situation all the console.logs get called EXCEPT the one that should be debounced (I tried both lodash debounce and my own, none worked; currently kept the lodash version).

So I tried to comment out that debounce call and tried to use it like that:

useEffect(() => {
    console.log('use effect 1')
    debounce(() => {
      console.log('deb: ' + movieSearchString)
    }, 1000)
  }, [movieSearchString])

I’m getting the use effect 1 log when the movieSearchString changes, but not the debounced one.

So I tried to do this:

const debounced = useRef(debounce(() => {
  console.log('deb: ' + movieSearchString)
}, 1000))
  
useEffect(() => debounced.current(movieSearchString), [movieSearchString])

In this case I’m getting the console log deb: after a second, but no movieSearchString is printed.

I don’t know what else I can do here… Eventually what I want is when a user enters something in the text field, I want to send an API call with the entered string. I don’t want to do it on every key stroke, of course, thus need the debounce. Any help, please?

Answer

Try to debounce the state value, not to debounce effect itself. It’s easier to understand.

For example, you can use custom hook useDebounce in your project:

useDebounce.js

// borrowed from https://usehooks.com/useDebounce/
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 re-call effect if value or delay changes
  );
  return debouncedValue;
}

App.js

const [movieSearchString, setMovieSearchString] = useState('')
const debouncedMovieSearchString = useDebounce(movieSearchString, 300);

const movieInputChangeHandler = string => {
  setMovieSearchString(string)
}

useEffect(() => {
  console.log('see useDebounceValue in action', debouncedMovieSearchString);
}, [debouncedMovieSearchString]);

useEffect(() => {
  const params = {
      apikey: 'somekey'
  }
    
  if (movieSearchString.length > 2) {
    params.search = debouncedMovieSearchString
  }

  callApi(params);
}, [debouncedMovieSearchString]);

Refer to this article: https://usehooks.com/useDebounce/