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?
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.
While this is true, we do not have to go the extreme of arranging for even function not to change identity at all.
Simple steps will be:
Declare event function using useCallback
- dependency list of useCallback
should include all the stateful variables that your function depends on.
Use useEffect
to add the event listener. Return cleanup function that will remove the event listener. Dependency list of useEffect
should include the event listener function itself, in addition to any other stateful variable your effect function might be using.
This way, when any of stateful variable used by event listener changes, even listener's identity changes, which will trigger running of the effect, but before running the effect cleanup function returned by previous run of the effect will be run, properly removing old event listener before adding the new one.
Something on the lines of:
const Component = () => {
const [state, setState] = useState();
const eventListner = useCallback(() => {
console.log(state); // use the stateful variable in event listener
}, [state]);
useEffect(() => {
el.addEventListner('someEvent', eventListner);
return () => el.removeEventListener('someEvent', eventListner);
}, [eventListener]);
}
@ckedar 's solution can solve this question, but it has performance problem, when the eventListener change, react will remove and addEvent on the dom。
you can use useRef()
instead useState()
,if you want listen state change, you can use useStateRef()
:
import React, { useEffect, useRef, useState } from 'react';
export default function useStateRef(initialValue:any): Array<any>{
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
},[value])
return [value,setValue,ref];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With