Skip to content

Instantly share code, notes, and snippets.

@gragland
Last active June 26, 2022 17:04
Show Gist options
  • Save gragland/ae701852ae6159c712d860a946cd7ca0 to your computer and use it in GitHub Desktop.
Save gragland/ae701852ae6159c712d860a946cd7ca0 to your computer and use it in GitHub Desktop.
import { useState, useRef, useEffect, useCallback } from 'react';
// Usage
function App(){
// State for storing mouse coordinates
const [coords, setCoords] = useState({ x: 0, y: 0 });
// Event handler utilizing useCallback ...
// ... so that reference never changes.
const handler = useCallback(
({ clientX, clientY }) => {
// Update coordinates
setCoords({ x: clientX, y: clientY });
},
[setCoords]
);
// Add event listener using our hook
useEventListener('mousemove', handler);
return (
<h1>
The mouse position is ({coords.x}, {coords.y})
</h1>
);
}
// Hook
function useEventListener(eventName, handler, element = window){
// Create a ref that stores handler
const savedHandler = useRef();
// Update ref.current value if handler changes.
// This allows our effect below to always get latest handler ...
// ... without us needing to pass it in effect deps array ...
// ... and potentially cause effect to re-run every render.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(
() => {
// Make sure element supports addEventListener
// On
const isSupported = element && element.addEventListener;
if (!isSupported) return;
// Create event listener that calls handler function stored in ref
const eventListener = event => savedHandler.current(event);
// Add event listener
element.addEventListener(eventName, eventListener);
// Remove event listener on cleanup
return () => {
element.removeEventListener(eventName, eventListener);
};
},
[eventName, element] // Re-run if eventName or element changes
);
};
@setbap
Copy link

setbap commented Sep 14, 2019

hello .that good to attach an event to HTML node
so I add a state to reference HTML node ( i can't speak English well :) )
the code that I forked from code sandbox:https://codesandbox.io/s/useeventlistener-yyxnq
ref, setRef is optional, if we don't use it evet attached to the window
[i think that not be in a nice way but I learn to react and I want to suggest my way to help me learn new thing]

@yossisp
Copy link

yossisp commented Jan 16, 2020

there's a bug on line 38 in this code:

useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

if the handler is changed then removeEventListener must also be called.

@jedwards1211
Copy link

@yossisp it adds the real event listener just once on mount and removes it once on dismount, and the real event listener delegates to handler:

      // Create event listener that calls handler function stored in ref
      const eventListener = event => savedHandler.current(event);

@yossisp
Copy link

yossisp commented Jan 20, 2020

@jedwards1211 the problem is that event listeners are removed only when the component unmounts. But if the component rerenders the element will change which will add an event listener without removing the old one. This is what happened to me.

@jedwards1211
Copy link

@yossisp I don't know what you observed in the debugger but you must be misunderstanding, because the semantics of useEffect guarantees that the cleanup function will run (removing the old listener) before the function runs again (adding the new listener). And the cleanup function's closure will have element bound to the old element.

@larts85
Copy link

larts85 commented Nov 30, 2021

You may add the following line element = !element ? window : element before isSupported constant in order to avoid the following error window is not defined in Next.Js projects. Also remove default element value in hook parameters!

@TusharShahi
Copy link

I am wondering if the props like eventName,event and listener will not change then why run effects again. If useEventListener is run again why should we care about the new values, since the listener is already attached in the first place. What case am i missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment