Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import { useRef, useEffect } from 'react';
// Usage
function App(){
// State for storing mouse coordinates
const [coords, setCoords] = useState([0, 0]);
// Add event listener using our hook
useEventListener('mousemove', ({ clientX, clientY }) => {
// Update coordinates
setCoords([clientX, clientY]);
});
return (
<h1>
The mouse position is ({coords.x}, {coords.y})
</h1>
);
}
// Hook
function useEventListener(eventName, handler, element = global){
// 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
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
);
};
@wikt0r

This comment has been minimized.

Copy link

commented Mar 28, 2019

I think useRef in useEventListener is useless, because you always pass new anonymous function to the hook in every render from App. Try insert a console.log before the line savedHandler.current = handler; It will run on every render.

https://codesandbox.io/s/mqxy739vyy

Hooks are tricky. :)

@msevestre

This comment has been minimized.

Copy link

commented Mar 28, 2019

I was about to mention the same problem as @wikt0r
Using a useCallback in the caller would probably do the trick? It kinds of leaks the abstraction a bit however

@msevestre

This comment has been minimized.

Copy link

commented Mar 28, 2019

Yeah modifying it like that would work I believe:

// Usage
function App(){
  // State for storing mouse coordinates
  const [coords, setCoords] = useState([0, 0]);
  
const eventListener = useCallback(
    ({ clientX, clientY }) => {
      setCoords([clientX, clientY]);
    },
    [setCoords]
  );

  // Add event listener using our hook
  useEventListener("mousemove", eventListener);  
  return (
    <h1>
      The mouse position is ({coords.x}, {coords.y})
    </h1>
  );
}
...

Putting a console log in the first useEffect only shows one update

@oygen87

This comment has been minimized.

Copy link

commented Apr 15, 2019

we display the coords as ({coords.x}, {coords.y}) .

but where do we define x and y keys on coords?

shouldnt we display them as ({coords[0]}, {coords[1]}) ?

@ikabirov

This comment has been minimized.

Copy link

commented Apr 19, 2019

Element in hook parameter doesn't work. We can't get element in first render call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.