React 19.2’s useEffectEvent vs our Custom useEventCallback

react-19.2’s-useeffectevent-vs-our-custom-useeventcallback

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:

  1. React’s internal magic > our clever hacks, so maybe we’re missing some React optimizations
  2. 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. 😅

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
cercle:-parra-for-cuva-–-nightjar-(live-version)-|-cercle-odyssey

Cercle: Parra for Cuva – Nightjar (Live Version) | Cercle Odyssey

Related Posts