I have a component that uses event listeners in several places through addEventListener
and removeEventListener
. It's not sufficient to use component methods like onMouseMove
because I need to detect events outside the component as well.
I use hooks in the component, several of which have the dependency-array at the end, in particular useCallback(eventFunction, dependencies)
with the event functions to be used with the listeners. The dependencies are typically stateful variables declared using useState
.
From what I can tell, the identity of the function is significant in add/remove
EventListener
, so that if the function changes in between it doesn't work. At first i tried managing the hooks so that the event functions didn't change identity between add
and remove
but that quickly became unwieldy with the functions' dependency on state.
So in the end I came up with the following pattern: Since the setter-function (the second input parameter to useState
) gets the current state as an argument, I can have event functions that never change after first render (do we still call this mount?) but still have access to up-to-date stateful variables. An example:
import React, { useCallback, useEffect, useState } from 'react';
const Component = () => {
const [state, setState] = useState(null);
const handleMouseMove = useCallback(() => {
setState((currentState) => {
// ... do something that involves currentState ...
return currentState;
});
}, []);
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [/* ... some parameters here ... */]);
// ... more effects etc ...
return <span>test</span>;
};
(This is a much simplified illustration).
This seems to work fine, but I'm not sure if it feels quite right - using a setter function that never changes the state but just as a hack to access the current state.
Also, for event functions that require several state variables I have to nest the setter calls.
Is there another pattern that could handle this situation in a nicer way?
state
here as that is the current state. Are you setting state to trigger react to re-render a component? – SchulteonMouseMove
is updated whenstate
changes, i.e. supplystate
in the dependency array. And then the call toremoveEventListener
will not work because it's no longer the same effect function – LeslieonMouseMove
should have only a dependency on the mouse moving in whichever element the event listener is attached to, window here. What is the goal here? – SchulteonMouseMove
depends, among other things, on a list of references to the children components (that is used to check where they are, usinggetBoundingClientRect
) – Leslie