Read the Latest Value Without Re-Running the Effect
useEffectEvent
`useEffectEvent` (still in canary at time of writing) carves the non-reactive part out of an effect — useful when you want to read fresh state without including it in deps.
What you'll learn
- Recognize when an effect dep "should not" trigger re-runs
- Use `useEffectEvent` to separate reactive setup from non-reactive logic
A common pain point: your effect reads some state, but you don’t want changes to that state to re-run the effect.
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const conn = connect(roomId);
conn.on("message", msg => showToast(theme, msg));
return () => conn.disconnect();
}, [roomId, theme]); // theme change → reconnects? Probably not what we want
} theme is only used for the visual toast — it shouldn’t cause a
reconnection. But omitting it from deps means stale closures.
The Hook
useEffectEvent(fn) returns a stable function that always sees the
latest props/state when called, but isn’t itself reactive.
function ChatRoom({ roomId, theme }) {
const onMessage = useEffectEvent(msg => {
showToast(theme, msg); // reads latest theme
});
useEffect(() => {
const conn = connect(roomId);
conn.on("message", onMessage);
return () => conn.disconnect();
}, [roomId]); // theme NOT in deps — it's encapsulated by useEffectEvent
} Now theme is read fresh each time onMessage is called, but
changing theme does NOT cause the effect to tear down and re-run.
When To Reach For It
The signal is: “I have an effect dep that’s only used in a callback, and listing it forces unnecessary reconnections/re-fetches”.
If the value affects setup (the URL, the connection key, the
subscription parameters), it belongs in deps. If it only affects
side reactions (toasts, logging, analytics), wrap them in
useEffectEvent.
Status
useEffectEvent is the modern answer to a very common problem and
is shipping in React. If your version doesn’t have it yet, the
workaround is a ref:
const themeRef = useRef(theme);
useEffect(() => { themeRef.current = theme; }, [theme]);
useEffect(() => {
const conn = connect(roomId);
conn.on("message", msg => showToast(themeRef.current, msg));
return () => conn.disconnect();
}, [roomId]); Same idea — refs aren’t reactive, so the effect doesn’t re-run when
theme changes.
Chapter Done
That’s every built-in hook plus custom hooks. Next chapter: patterns and the rest of React 19’s toolkit.
Composition Over Configuration →