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.
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.
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.
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
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.
Frequently Asked Questions
What does useEffectEvent do?
Is useEffectEvent available in the stable release?
Can I pass an Effect Event to another 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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.