Andrew's Digital Garden

useEffectEvent - separating effects from reactive values

This is released as of React 19.2 - it helps with one of the big problems with [[20220815044638-useeffect]], separating reactive and non-reactive logic.

You should use useEffectEvent for functions that are conceptually “events” that happen to be fired from an Effect instead of a user event (that’s what makes it an “Effect Event”). You don’t need to wrap everything in useEffectEvent, or to use it just to silence the lint error, as this can lead to bugs.

Props, state, and variables declared inside your component’s body are called reactive values. Reactive values can change due to a re-render. Logic inside effects is reactive. Logic inside event handlers is not reactive. It does not run automatically. Event handlers can read reactive values without “reacting” to their changes.

function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); // ...

As both roomId and theme are reactive, both should be in the deps array.

function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); return () => { connection.disconnect() }; }, [roomId, theme]); // ✅ All dependencies declared // ...

However, You don't want this to execute based on the theme changing, only if the roomId does. The above code has an issue - if the theme changes, it will re-connect to the room.

You need a way to separate this non-reactive logic from the reactive Effect around it.

React has some suggestions on how to solve this problem (https://react.dev/learn/removing-effect-dependencies#removing-unnecessary-dependencies). Which are loosely:

This is where useEffectEvent comes in. In the above example, it allows you to separate the non-reactive logic out:

function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Connected!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); // ✅ All dependencies declared // ...

React docs mentions the following:

After useEffectEvent becomes a stable part of React, we recommend never suppressing the linter.

Similar to DOM events, Effect Events always “see” the latest props and state. Effect Events should not be declared in the dependency array

https://react.dev/learn/separating-events-from-effects

[[react]]

useEffectEvent - separating effects from reactive values