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]); `
...
}
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.