Hey fellow React devs! 👋
So React 19.2 dropped and brought us useEffectEvent
– a new hook that solves a problem many of us have been tackling with custom solutions. If you’re like me and have been using a custom useEventCallback
hook (or something similar), you’re probably wondering: “Should I ditch my trusty custom hook for this new official one?”
BTW: if you interested to read it from medium.
Let me break it down for you based on some real-world experience.
The Problem Both Hooks Solve
First, let’s talk about why these hooks exist. You know that annoying situation where you want to access the latest state/props/reference in a useEffect
without adding them to the dependency array? Yeah, that one:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onNavigate = useCallback((visitedUrl) => {
logVisit(visitedUrl, numberOfItems);
},[numberOfItems]);
useEffect(() => {
onNavigate(url);
}, [url, onNavigate]); // if not passing onNavigate getting lint error
// ...
}
In this example, the Effect should re-run after a render when url
changes (to log the new page visit), but it should not re-run when numberOfItems
changes that creates a new ref for onNavigate
causing new log for every numberOfItems
up and down!
So we need a way to get a persistent ref for onNavigate
function including the latest numberOfItems
.
Now if you use new hook useEffectEvent
instead of useCallback
this problem gets solved:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onNavigate = useEffectEvent((visitedUrl) => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onNavigate(url);
}, [url]);
// ...
}
This problem already gets solved by another hook called useEventCallback
by creating stable function reference.
Meet the Custom Champion: useEventCallback
This is what many of us have been building:
export function useEventCallback<T extends (...args: any[]) => any>(fn: T): T {
const ref = useRef(fn)
useEffect(() => {
ref.current = fn
})
return useCallback((...args: any[]) => ref.current.apply(void 0, args), []) as T
}
It’s reliable, battle-tested, and works everywhere.
CAN We Now Swap Them?
Here’s where it gets interesting. These hooks are not drop-in replacements for each other.
useEventCallback instead of useEffectEvent:
Yes you can, the result is almost the same, example:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onNavigate = useEventCallback((visitedUrl) => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onNavigate(url);
}, [url]);
// ...
}
But there are some caveats:
- React’s internal magic > our clever hacks, so maybe we’re missing some React optimizations
- The official API is designed for React’s future – You need perfect Concurrent Mode compliance
useEffectEvent instead of useEventCallback:
✅ You CAN use useEffectEvent when you need stable event handlers for Effect deps
// ✅ useEffectEvent works
const handleClick = useEventCallback(() => doSomething());
useEffect(() => {
handleClick()
}, [])
❌ You CANNOT use useEffectEvent when:
1. You need event handlers for JSX
// ✅ useEventCallback works
const handleClick = useEventCallback(() => doSomething());
return <button onClick={handleClick}>Click me</button>;
// ❌ useEffectEvent is NOT for this
2. You’re passing callbacks to child components
// ✅ Safe with useEventCallback
const onChildEvent = useEventCallback(handleChildEvent);
return <ChildComponent onEvent={onChildEvent} />;
// ❌ Don't pass useEffectEvent callbacks as props
3. You need async operations
// ✅ useEventCallback handles async fine
const handleAsync = useEventCallback(async (id) => {
const result = await fetchData(id);
return result;
});
// ❌ useEffectEvent has restrictions with async
My Take: Use Both (For Now)
Here’s my practical recommendation:
-
Keep using
useEventCallback
for general stable function references, event handlers, and callback props -
Adopt
useEffectEvent
specifically for functions called within effects
They serve different purposes, even though they solve similar problems.
The Migration Path
If you’re on React 19.2+, start migrating effect-specific uses:
// Before
const logEvent = useEventCallback((data) => {
analytics.track('event', data, currentUser.id);
});
useEffect(() => {
logEvent(someData);
}, [someData]);
// After
const logEvent = useEffectEvent((data) => {
analytics.track('event', data, currentUser.id);
});
useEffect(() => {
logEvent(someData);
}, [someData]);
But keep useEventCallback
for everything else!
The Bottom Line
Don’t feel bad about your custom hook – it’s been doing great work! useEffectEvent
isn’t here to replace it entirely, but to provide a more specialized, optimized solution for a specific use case.
What’s your experience been with these hooks? Are you planning to migrate, or sticking with your custom solutions? Drop a comment – I’d love to hear your thoughts!
P.S. – If you found this helpful, give it a clap! And if you have war stories about effect dependencies gone wrong, I’m all ears. We’ve all been there. 😅