How can I avoid wrapping a bunch of functions inside useCallback when using useEffect

66 Views Asked by At

I'm making a pixel drawing app.
I have a canvas component which handles the drawing logic, inside of which is the following useEffect:

...
useEffect(() => {

        // Pass reference of the canvas element to the parent component 
        setCanvasRef(canvasRef.current);

        // Mouse util -> add listeners and offset the coordinates relative to the drawing canvas element
        mouse.follow(canvasRef.current);

       
        // Add listeners (not using synthetic react listeners because I need to track events              
        // outside the canvas bounds)
    
        document.addEventListener('mousedown', executeCurrentState);
        document.addEventListener('mouseup', executeCurrentState);
        document.addEventListener('mousemove', executeCurrentState);

        // Render initial pixel data to the canvas
        render();

        // Cleanup
        return () => {
            document.removeEventListener('mousedown', executeCurrentState);
            document.removeEventListener('mouseup', executeCurrentState);
            document.removeEventListener('mousemove', executeCurrentState);
            mouse.removeListeners();
        };
        
    }, [setCanvasRef, mouse, render, executeCurrentState]);
...

React tells me to wrap the render and executeCurrentState functions inside a useCallback,
But since these functions call other functions, I have to add those to the useCallback dependency array, and wrap them in a useCallback too.
It goes on like this recuresively until every function and property called by render and executeCurrentState is wrapped in useCallback.

How can I avoid this?

1

There are 1 best solutions below

2
Azzy On BEST ANSWER

refering a function with a ref could work

   const SomeComponent = () => {

      const executeCurrentState = () => {  
        // use a prop or state
      }
 
      const executeCurrentStateRef = useRef();
      executeCurrentStateRef.current = executeCurrentState;

      useEffect(() => {
         ...
         const executeCurrentStateWrapper = (event) => {
              executeCurrentStateRef.current(event) // always points to latest function
         }

         document.addEventListener('mousedown', executeCurrentStateWrapper);
         ...
         return  () => {
             document.removeEventListener('mousedown', executeCurrentStateWrapper);
         }


      , [])

if you are using React 18 and want to try out their new (experimental) useEvent hook

  const executeCurrentState = useEvent(() => {
       // code for function in component render
   });

   useEffect(() => {

             document.addEventListener('mousedown', executeCurrentState);
             ...
             return  () => {
                 document.removeEventListener('mousedown', executeCurrentState);
             }

    }, []) // no dependencies needed for userEvent