2

I'm hoping someone can explain to me the correct usage of React hook in this instance, as I can't seem to find away around it.

The following is my code

  useEffect(() => {
    _getUsers()
  }, [page, perPage, order, type])

  // This is a trick so that the debounce doesn't run on initial page load
  //  we use a ref, and set it to true, then set it to false after
  const firstUpdate = React.useRef(true);
  const UserSearchTimer = React.useRef()
  useEffect(() => {
    if(firstUpdate.current)
      firstUpdate.current = false;
    else 
      _debounceSearch()
  }, [search])

  function _debounceSearch() {
    clearTimeout(UserSearchTimer.current);
    UserSearchTimer.current = setTimeout( async () => {
        _getUsers();
    }, DEBOUNCE_TIMER);
  }

  async function _getUsers(query = {}) {
    if(type) query.type = type;
    if(search) query.search = search;

    if(order.orderBy && order.order) {
      query.orderBy = order.orderBy;
      query.order = order.order;
    }

    query.page = page+1;
    query.perPage = perPage;

    setLoading(true);
    try {
      await get(query);
    }
    catch(error) {
      console.log(error);
      props.onError(error);
    }
    setLoading(false);
  }

So essentially I have a table in which i am displaying users, when the page changes, or the perPage, or the order, or the type changes, i want to requery my user list so i have a useEffect for that case.

Now generally I would put the _getUsers() function into that useEffect, but the only problem is that i have another useEffect which is used for when my user starts searching in the searchbox.

I don't want to requery my user list with each and every single letter my user types into the box, but instead I want to use a debouncer that will fire after the user has stopped typing.

So naturally i would create a useEffect, that would watch the value search, everytime search changes, i would call my _debounceSearch function.

Now my problem is that i can't seem to get rid of the React dependency warning because i'm missing _getUsers function in my first useEffect dependencies, which is being used by my _debounceSearch fn, and in my second useEffect i'm missing _debounceSearch in my second useEffect dependencies.

How could i rewrite this the "correct" way, so that I won't end up with React warning about missing dependencies?

Thanks in advance!

2
  • I use useEffect two ways, as a subscription and like this: "If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run." Would this work?: useEffect(() => { _getUsers() }, []) Commented Sep 17, 2019 at 2:19
  • No this wouldn't work because it's a subscription to _getUsers, even if i just did that it would also give me a warning about _getUsers needing to be a dependency, this is the exhaustive react deps warning Commented Sep 17, 2019 at 18:34

2 Answers 2

1

I would setup a state variable to hold debounced search string, and use it in effect for fetching users.

Assuming your component gets the query params as props, it would something like this:

function Component({page, perPage, order, type, search}) {
    const [debouncedSearch, setDebouncedSearch] = useState(search);

    const debounceTimer = useRef(null);
    // debounce
    useEffect(() => {
        if(debounceTime.current) {
            clearTimeout(UserSearchTimer.current);
        }
        debounceTime.current = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_DELAY);
    }, [search]);

    // fetch 
    useEffect(() => {
        async function _getUsers(query = {}) {
            if(type) query.type = type;
            if(debouncedSearch) query.search = debouncedSearch;

            if(order.orderBy && order.order) {
              query.orderBy = order.orderBy;
              query.order = order.order;
            }

            query.page = page+1;
            query.perPage = perPage;

            setLoading(true);
            try {
              await get(query);
            }
            catch(error) {
              console.log(error);
              props.onError(error);
            }
            setLoading(false);
        }
        _getUsers();
    }, [page, perPage, order, type, debouncedSearch]);
}

On initial render, debounce effect will setup a debounce timer... but it is okay.
After debounce delay, it will set deboucedSearch state to same value.
As deboucedSearch has not changed, ferch effect will not run, so no wasted fetch.

Subsequently, on change of any query param except search, fetch effect will run immediately. On change of search param, fetch effect will run after debouncing.

Ideally though, debouncing should be done at <input /> of search param. Small issue with doing debouncing in fetching component is that every change in search will go through debouncing, even if it is happening through means other than typing in text box, say e.g. clicking on links of pre-configured searches.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah i get it, so you would leverage useState with another variable called debouncedSearch, when that changes it'll rerender the useEffect. basically the debouncedSearch should = the search value at any point in time when the user has finished typing and the debounce timer has fired.
0

The rule around hook dependencies is pretty simple and straight forward: if the hook function use or refer to any variables from the scope of the component, you should consider to add it into the dependency list (https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies).

With your code, there are couple of things you should be aware of:

1.With the first _getUsers useEffect:

useEffect(() => {
    _getUsers()
  }, [page, perPage, order, type])

 // Correctly it should be:
 useEffect(() => {
    _getUsers()
  }, [_getUsers])

Also, your _getUsers function is currently recreated every single time the component is rerendered, you can consider to use React.useCallback to memoize it.

2.The second useEffect

useEffect(() => {
    if(firstUpdate.current)
      firstUpdate.current = false;
    else 
      _debounceSearch()
  }, [search])

// Correctly it should be
useEffect(() => {
    if(firstUpdate.current)
      firstUpdate.current = false;
    else 
      _debounceSearch()
  }, [firstUpdate, _debounceSearch])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.