How to Register Event with Useeffect Hooks

How to register event with useEffect hooks?

The best way to go about such scenarios is to see what you are doing in the event handler.

If you are simply setting state using previous state, it's best to use the callback pattern and register the event listeners only on initial mount.

If you do not use the callback pattern, the listeners reference along with its lexical scope is being used by the event listener but a new function is created with updated closure on each render; hence in the handler you will not be able to access the updated state

const [userText, setUserText] = useState("");
const handleUserKeyPress = useCallback(event => {
const { key, keyCode } = event;
if(keyCode === 32 || (keyCode >= 65 && keyCode <= 90)){
setUserText(prevUserText => `${prevUserText}${key}`);
}
}, []);

useEffect(() => {
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, [handleUserKeyPress]);

return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);

Why do we add eventListeners in useEffect hook

Because it is a "side effect". A side effect is something that affects your component. If you know about pure functions in JavaScript, think about it in the same way.

For instance, a pure function is something that will always return the same value given the same arguments:

function add(a, b) {
return a + b;
}

An impure function is one that has side-effects; i.e. it can output a different output every time:

function add(a, b) {
let num = Math.random()
return a + b + num;
}

With React, a side effect is something that can't be done during render time, so something like adding an event listener goes in useEffect because it has nothing to do with the components state or received props.

How to add Event Listeners to UseRefs within UseEffect

You can just past offeringsContainer to the ref of the component. useEffect will be invoked only when there is first rendering that's why your offeringsContainer.current will not be null.

And you forgot to remove listener after the component will be unmounted.

Your code should be like this;

const offeringsContainer = useRef(null);

useEffect(() => {
checkHeaderState();
offeringsContainer.current.addEventListener(
"wheel",
onOfferingsContainerWheel,
{ passive: false }
);

return () => offeringsContainer.current.removeEventListener("wheel");
}, []);

return (
<Fragment>
<div className="card-business-products-services-title-text">
Products & Services
</div>
<div
className="card-business-products-services-container"
id="card-business-products-services-container"
onWheel={onOfferingsContainerWheel}
ref={offeringsContainer}
>
{renderOfferings()}
</div>
</Fragment>
);

Example: https://codesandbox.io/s/wizardly-banzai-3fhju?file=/src/App.js

Using useEffect with event listeners

So your problem is that you pass an empty array as the second argument to your effect so the effect will never be cleaned up and fired again. This means that handleClick will only ever be closed over the default state. You've essentially written: setNameFromEventHandler("Colin"); for the entire life of this component.

Try removing the second argument all together so the effect will be cleaned up and fired whenever the state changes. When the effect refires, the function that will be handling the click event that will be closed over the most recent version of your state. Also, return a function from your useEffect that will remove your event listener.

E.g.

  useEffect(() => {
document.getElementById("name").addEventListener("click", handleClick);
return () => {
document.getElementById("name").removeEventListener("click", handleClick);
}
});

Setting event handlers on react hooks (useEffect)

Issue

You are enclosing the initial state in the attached callback by only setting the callback when the component mounts using an empty dependency array. A functional state update won't have this issue as it uses a callback function to compute and return a new state value from the previous state value.

Solution

  • The effect needs to add an event listener correctly. (and return a cleanup function!!)
  • State update should be a functional update since it depends on previous state.
  • State updates are asynchronous in nature, so alerting or logging it right after it is enqueued will only log the current state value from the current render cycle. Use an effect to do anything with the updated state.

Updated code

const Hello = () => {
const [counter, setCounter] = React.useState(0);
let canvas = React.useRef();

const add = () => {
setCounter(counter => counter + 3);
};

React.useEffect(() => {
console.log(counter); // or alert, if you really want to
}, [counter]);

React.useEffect(() => {
window.addEventListener("keydown", add);
return () => window.removeEventListener("keydown", add);
}, []);

return (
<div>
<h1>{counter}</h1>
</div>
);
};

Edit Add window event listener

When should I use useEffect hook instead of event listeners?

I think if you can do something in event handlers then you should do them there instead of useEffect.

Here are some additional quotes.

Docs:

In React, side effects usually belong inside event handlers. Event
handlers are functions that React runs when you perform some
action—for example, when you click a button. Even though event
handlers are defined inside your component, they don’t run during
rendering! So event handlers don’t need to be pure.

If you’ve exhausted all other options and can’t find the right event
handler for your side effect, you can still attach it to your returned
JSX with a useEffect call in your component. This tells React to
execute it later, after rendering, when side effects are allowed.
However, this approach should be your last resort.

Also related quote by Dan Abramov:

To sum up, if something happens because a user did something,
useEffect might not be the best tool.

On the other hand, if an effect merely synchronizes something (Google
Map coordinates on a widget) to the current state, useEffect is a good
tool. And it can safely over-fire.

How useState works with eventListener in react

This is the best I can explain:

For the context, when you click the toggle flag button, only your first useEffect(callback,[handler]) runs after EVERY rendering and your second useEffect(callback,[eventName,element]) runs ONLY ONCE after the initial rendering.

  1. Now when you pass savedHandler.current directly to the event listener, line 21, the event listener attaches function returned by useCallback as it is and hence on every event, the same function is being called due to reasons: The event is registered only ONCE and the cleanup function does not run since the useEffect runs only once after the initial rendering(the cleanup function will run in instances, just before the next side effect due to dependency changes, and when the component unmount). So you are registering the event only once with a memoized callback which persist across renderings. The update of savedHandler.current in the first useEffect doesn't update the callback passed to event listener in the second useEffect since it doesn't re-run and hence doesn't update the callback passed to the event listener.
  2. Now when you use savedHandler.current function inside an anonymous function, the scenario is completely different here. When you pass such functions as a callback, on every event a new function is being invoked unlike the first instance. The anonymous callback of first event and second event is not same despite having the same code. So here you are not stuck with the same event listener callback that you passed previously and hence you have now access to the latest memoized savedHandler.current value inside the callback function updated by the first useEffect despite the second effect not running again.

This is it. To confirm for yourself, try to add handler as the dependency on your second useEffect as well, and pass savedHandler.current directly to the event listener. You will get the updated state value as the useEffect now runs after every update of handler and the event listener gets the latest callback to invoke upon.
And instead of creating another variable, you can directly do element.addEventListener(eventName, (event) => savedHandler.current(event));



Related Topics



Leave a reply



Submit