[React] useEffectEvent: A New Hook For 'Events' inside 'useEffect'
Updated
With the release of version 19.2, a new hook named useEffectEvent
was introduced in React. As the name suggests, it is for the "events" defined inside useEffect
. But what exactly is an "Event" and how does it affect the "Effect"? That's what I intend to discuss here in this post.
useEffectEvent
hook addresses a very particular problem where because of exhaustive-deps rule of useEffect, dependencies which aren't supposed to trigger the "Effect" trigger it. One option is to suppress the rule and only specify the dependencies on which I actually want to trigger the Effect, but what if I actually use non-triggering dependencies inside the effect in some way or the other and always want their updated values? The values might be stale since I removed those non-triggering dependencies from the dependency array.
"Event" vs "Effect"
To understand this, the official React blog breaks down the code inside useEffect and puts it into two categories: Event and Effect.
An "Event" is what happens when the user does something or performs an action in the application. An "Effect" is something which is dependent on the reactive values in the application. I like to call the values which are controlled by React, reactive.
This distinction is crucial because it helps to extract Events from the useEffect code. Let me give an example.
Lets say I have a component which does something once I receive the payment confirmation from the payment gateway. Here, the props are reactive values, meaning they can change irrespective of the user input.
function Payments({ paymentStatus, cart, customerNote }) {
useEffect(() => {
if (paymentStatus === "confirmed") {
finalizeAndTrackOrder(cart, customerNote, paymentStatus);
}
}, [paymentStatus, cart, customerNote]);
}
finalizeAndTrackOrder
can be considered as an 'Event' because it only happens at a specific time and when the user actually completes the payment. The issue here is the cart
and customerNote
dependencies in the dependency array. I don't want to track the final order every time the cart or customerNote changes. So, technically cart and customerNote deps should not trigger the 'Effect', but I also want their latest values passed to the finalizeAndTrackOrder
function (if they change).
The finalizeAndTrackOrder
function can be categorized as an 'Event', whereas the paymentStatus
condition can be categorized as an 'Effect' which runs whenever the payment status updates.
The useEffectEvent
Hook
Now that I know that the 'Event' is the finalizeAndTrackOrder
function, I can take it out of the useEffect
hook and wrap it in the new useEffectEvent
hook.
function Payments({ paymentStatus, cart, customerNote }) {
// ...
const onPaymentConfirmed = useEffectEvent((paymentStatus) => {
finalizeAndTrackOrder(cart, customerNote, paymentStatus);
});
useEffect(() => {
if (paymentStatus === "confirmed") {
onPaymentConfirmed(paymentStatus);
}
}, [paymentStatus]);
// ...
}
The code inside useEffectEvent
hook will always access the latest reactive values. However, there are two limitations on how to use "Effect Events":
- Don't pass "Effect Events" to other components or hooks.
- They can only be called from inside the
useEffect
hook.
Thoughts
The useEffectEvent
hook solves a very particular problem, but I am wondering, is the useEffect hook flawed by design? The need to introduce new hooks around it makes it evident to some extent. Or, is it that people don't know how to use it? That's a question to ask.