Search code examples
reactjsusecallback

How to add functions from props to useCallback dependency array?


The Button receives the onClose method as a prop, which is used in the handleKeyDown. The handleKeyDown itself is added as a dependency for the useEffect, therefore I wrapped it with a useCallback so in case I want to add a state change in the future in the useEffect, this won't cause an infinite rerender. However, I have to add the onClose in the dependency of the useCallback wrapped around the handleKeyDown. As the onClose comes from props, this will change at every re-render, causing the handleKeyDown to be created at every re-render. Of course, a solution would be to wrap the onClose in a useCallback before passing it to the CloseButton and use React.memo on the component. However, I want to handle this from inside the component instead of outsourcing it. Could you please confirm if there is a way to solve this issue? Thank you.

    const Button: React.FC<ButtonProps> = ({ onClose ...props }) => {

    const onKeyDown = (event: KeyboardEvent) => {
        if (event.keyCode === 65) {
            onClose(event);
        }
    };

    useEffect(() => {
        window.addEventListener('keydown', onKeyDown);

        return () => {
            window.removeEventListener('keydown', onKeyDown);
        };
    }, [onKeyDown]); `

...
}


Solution

  • The official way to solve this is to use useCallback in the parent like you said (but there is no need to use React.memo though) and I would recommend doing it this way to avoid bugs.

    If you have some good reason for not using this approach, you could do it by useRef like this:

    const Button: React.FC<ButtonProps> = ({ onClose ...props }) => {
        const onCloseRef = useRef(onClose);
        onCloseRef.current = onClose;
    
        const onKeyDown = useCallback((event: KeyboardEvent) => {
            if (event.keyCode === 65) {
                onCloseRef.current(event);
            }
        }, [onCloseRef];
    
        useEffect(() => {
            window.addEventListener('keydown', onKeyDown);
    
            return () => {
                window.removeEventListener('keydown', onKeyDown);
            };
        }, [onKeyDown]);
    
        ...
    }
    

    But this approach has some pitfalls so I would avoid it if possible.