Search code examples
javascriptreactjsreact-hooksusecallback

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


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?


Solution

  • 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