Search code examples
reactjsuse-effectusecallback

Every function called in `useEffect` stack must be wrapped in `useCallback`?


I am new to React and it seems to me that if you use a function inside of useEffect, that entire stack has to be wrapped in useCallback in order to comply with the linter.

For example:

const Foo = ({} => {
  const someRef = useRef(0);

  useEffect(() => {
    startProcessWithRef();
  }, [startProcessWithRef]);

  const handleProcessWithRef = useCallback((event) => {
    someRef.current = event.clientY;
  }, []);

  const startProcessWithRef = useCallback(() => {
    window.addEventListener('mousemove', handleProcessWithRef);
  }, [handleProcessWithRef]);

  ...
});

I'm wondering if there is a different pattern where I don't have to make the entire chain starting in useEffect calling startProcessWithRef be wrapped in useCallback with dependencies. I am not saying it is good or bad, I'm just seeing if there is a preferred alternative because I am new and don't know of one.


Solution

  • The idiomatic way to write your example would be similar to this:

    Note the importance of removing the event listener in the effect cleanup function.

    TS Playground

    import {useEffect, useRef} from 'react';
    
    const Example = () => {
      const someRef = useRef(0);
    
      useEffect(() => {
        const handleMouseMove = (event) => { someRef.current = event.clientY; };
        window.addEventListener('mousemove', handleMouseMove);
        return () => window.removeEventListener('mousemove', handleMouseMove);
      }, [someRef]);
    
      return null;
    };
    

    If you prefer defining the function outside the effect hook, you'll need useCallback:

    import {useCallback, useEffect, useRef} from 'react';
    
    const Example = () => {
      const someRef = useRef(0);
    
      const updateRefOnMouseMove = useCallback(() => {
        const handleMouseMove = (event) => { someRef.current = event.clientY; };
        window.addEventListener('mousemove', handleMouseMove);
        return () => window.removeEventListener('mousemove', handleMouseMove);
      }, [someRef]);
    
      useEffect(updateRefOnMouseMove, [updateRefOnMouseMove]);
    
      return null;
    };