React useEffectEvent Explained with Practical Examples

useEffectEvent reads the latest props and state from inside an Effect without re-running it. Learn how to separate events from Effects.

6 min read

React useEffectEvent is a Hook that lets you read the latest props and state from inside an Effect without making the Effect re-run. It separates the reactive part of an Effect from code that should behave like an event handler. It is the official way to read the latest values from non-reactive code, instead of suppressing the dependency linter.

Version requirement

useEffectEvent shipped as a stable Hook in React 19.2. If your app runs an older React version, check the current React reference before relying on it.

The problem it solves

An Effect re-runs when any value it reads changes. That is usually what you want, but some values should not restart the work.

App.jsxApp.jsx
import { useEffect, useState } from "react";
 
function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);
  useEffect(() => {
    const id = setInterval(() => setCount((c) => c + increment), 1000);
    return () => clearInterval(id);
  }, [increment]);
  return <p>{count}</p>;
}

The interval reads increment, so increment is a dependency. Every time the user changes the increment, the interval is cleared and recreated, which makes fast changes feel like the timer stalls. The stall is the visible symptom of the wrong dependency: the timer only needs to read increment on each tick, not to restart when it changes.

Declare an Effect Event

Wrap the non-reactive part in useEffectEvent. The returned function always reads the latest values without becoming a dependency. The wrapper looks like a plain function, but it is tied to the component that created it.

App.jsxApp.jsx
import { useEffect, useEffectEvent, useState } from "react";
 
function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);
  const onTick = useEffectEvent(() => setCount((c) => c + increment));
  useEffect(() => {
    const id = setInterval(() => onTick(), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>{count}</p>;
}

Now the interval is created once with an empty dependency array. Each tick calls onTick, which reads the current increment at that moment.

Changing the increment updates the next tick without restarting the timer. The interval itself was never recreated.

Rules to follow

Effect Events are limited by design. They keep the boundary between reactive and non-reactive code honest.

  • Call them only from inside Effects or other Effect Events.
  • Never call them during render or pass them to other components or Hooks.
  • Omit them from dependency arrays, because their identity changes every render.
  • Do not use them to hide a dependency that should re-run the Effect.

When the value genuinely should restart the work, keep it in the dependency array instead. Effect Events are for reading the latest value, not for skipping real re-synchronization. These limits exist so an Effect Event stays local to the Effect it belongs to, which is what makes the latest-value behavior safe.

What to learn next

Effect Events are the fix for one flavor of stale closure in React hooks. For the reactive half of the story, how the dependency array works is the companion concept.

Rune AI

Rune AI

Key Insights

  • useEffectEvent wraps non-reactive logic so it can read the latest values.
  • Effect Events are omitted from the dependency array.
  • Call Effect Events only from inside Effects, never during render or from other components.
  • Do not use them to hide real dependencies from the linter.
RunePowered by Rune AI

Frequently Asked Questions

What does useEffectEvent do?

It wraps a callback so it can read the latest props and state without becoming a dependency of the Effect that calls it.

Is useEffectEvent available in the stable release?

Yes. It shipped as a stable Hook in React 19.2. Check the current React reference if your app runs an older version.

Can I pass an Effect Event to another component?

No. Effect Events can only be called from inside Effects or other Effect Events in the same component.

Conclusion

useEffectEvent splits an Effect into reactive logic and event-like logic. The Effect re-runs when its real dependencies change, while the Effect Event always reads the latest values.