Igal
Igal

Reputation: 6083

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?

Upvotes: 0

Views: 1150

Answers (2)

szilardtumo
szilardtumo

Reputation: 201

You need to wrap the function you want to debounce wit the debounce() function like this:

const searchMovieHandler = debounce(() => {
    console.log('movie handler called')
    const params = {
      apikey: 'somekey'
    }
    
    if (movieSearchString.length > 2) {
      params.search = movieSearchString
    }
  }, 1000);

Upvotes: 0

glinda93
glinda93

Reputation: 8459

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/

Upvotes: 1

Related Questions