EdgeCases Logo
Dec 2025
React
Deep
5 min read

useEffectEvent: Solving Stale Closures Forever

Stop hacking refs—use the stable hook designed for non-reactive logic

react
hooks
useeffect
stale-closures
performance

The "stale closure" is the most common bug in React applications. You have a useEffect that needs to read a value (like a prop or state), but you don't want the effect to re-run when that value changes. So you omit it from the dependency array, and suddenly your effect is reading old data.

The Old Hack: useRef

For years, we solved this with the useRef pattern:

// The "latest ref" pattern
const onScrollRef = useRef(onScroll);
onScrollRef.current = onScroll;

useEffect(() => {
  const handleScroll = () => {
    // Read the latest value from the ref
    onScrollRef.current();
  };
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []); // Safe because refs are stable

It works, but it's verbose, ugly, and feels like a hack.

The Solution: useEffectEvent

React 19.2 introduces useEffectEvent (formerly useEvent). It creates a stable function handle that always has access to the latest props and state, but doesn't trigger re-renders.

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
  // 1. Create a stable event handler
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme); // Reads latest 'theme'
  });

  useEffect(() => {
    const connection = createConnection(roomId);
    connection.on('connected', () => {
      onConnected(); // Call the stable handler
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // 'theme' is NOT a dependency!
}

Reactive vs. Non-Reactive

This hook formalizes the separation between:

  • Reactive Code (useEffect): Code that should run when things change (e.g., connecting to a new room when roomId changes).
  • Non-Reactive Code (useEffectEvent): Code that should run in response to an event, reading the latest values without re-triggering the setup (e.g., showing a notification with the current theme).

It's the missing piece of the React reactivity puzzle.

Advertisement

Additional Resources

Explore these curated resources to deepen your understanding

Related Insights

Explore related edge cases and patterns

SEO
Expert
JavaScript Hydration and SEO: The Googlebot Race Condition
7 min
CSS
Deep
CSS Animations vs JavaScript: Layout Thrashing and the FLIP Technique
7 min

Advertisement