0

I am looking at the documentation from react regarding omitting functions from the list of dependencies because I got this error in my js linter. I have tried to amend my code like the docs show, but I feel like I am missing something, because I still get the same error. I will post my code before and after my attempt. Thanks.

warning  React Hook useEffect has a missing dependency: 'handleDocumentScrollThrottled'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

Code Before attempted fix

/* Completed useDocumentScrollThrottled utility function */

import { useEffect, useState } from 'react';
import { throttle } from 'lodash';

function useDocumentScrollThrottled(callback) {
  const [, setScrollPosition] = useState(0);
  let previousScrollTop = 200;

  function handleDocumentScroll() {
    const { scrollTop: currentScrollTop } = document.documentElement || document.body;

    setScrollPosition(previousPosition => {
      previousScrollTop = previousPosition;
      return currentScrollTop;
    });

    callback({ previousScrollTop, currentScrollTop });
  }

  const handleDocumentScrollThrottled = throttle(handleDocumentScroll, 250);

  useEffect(() => {
    window.addEventListener('scroll', handleDocumentScrollThrottled);

    return () =>
      window.removeEventListener('scroll', handleDocumentScrollThrottled);
  }, []);
}

export default useDocumentScrollThrottled;

Code after attempted fix

...

useEffect(() => {

    function doSomething(){
      window.addEventListener('scroll', handleDocumentScrollThrottled);

    return () =>
      window.removeEventListener('scroll', handleDocumentScrollThrottled);
    } 

    doSomething();

  }

  ...

1 Answer 1

1

The error will not get resolved, just because you are wrapping the dependency inside another function. The way that's suggested in the docs with the doSomething exmaple is to move the function out of your hook's scope. This is not possible in your case as your handleDocumentScroll depends on a setter that's only available in the hook.

Instead you have to:

Add the dependency of handleDocumentScrollThrottled to your dependency array like so:

  useEffect(() => {
    //...
  }, [handleDocumentScrollThrottled]);

This of course will not work, as handleDocumentScrollThrottled is reallocated with every render, therefore causing this effect to trigger every single time. That's very likely not what you wanted.

The solution here is to use a memoized value for handleDocumentScrollThrottled, so that it does not change on every re-render:

  const handleDocumentScrollThrottled = useMemo(() => {
    function handleDocumentScroll() {
      const { scrollTop: currentScrollTop } = document.documentElement || document.body;

      setScrollPosition(previousPosition => {
        previousScrollTop = previousPosition;
        return currentScrollTop;
      });

      callback({ previousScrollTop, currentScrollTop });
    }

    return throttle(handleDocumentScroll, 250);
  }, []);

However, I want to note that (ab)using a setter to set another variable is super hacky and you should probably use a ref to track your old scroll position instead.

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

2 Comments

Does this look like a better way to track scroll position using ref? dev.to/n8tb1t/tracking-scroll-position-with-react-hooks-3bbj
@AndersKitson Yes, this is what I meant. Using the ref allows you to instantly access the set value (when you're using a state setter this is not available, until the next render, which in your case is too late), but without introducing possible side effects.

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.