Проблема. Ефект має перезапускатися, коли змінюються одні значення, але використовує й інші, через які перезапускатися не повинен.
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => {
showNotification('Підключено', theme); // потрібна поточна тема
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // зміна теми перепідключає до чату!
}
Прибрати theme із залежностей не можна - буде застаріле значення, і лінтер це помітить. Додати - зайві перепідключення.
useEffectEvent (стабільний з React 19.2) виносить нереактивну частину логіки в «подію ефекту»: функцію, яка завжди бачить актуальні props і стан, але не є залежністю:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Підключено', theme);
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // лише справжня причина перепідключення
}
Як розрізняти:
- реактивна логіка (має перезапускати синхронізацію) - у тілі ефекту, її значення в залежностях;
- логіка-реакція (виконується в певний момент і має бачити свіжі значення) - у
useEffectEvent.
Інші типові випадки: аналітика з поточними даними користувача при зміні сторінки, обробник інтервалу, що читає свіжий стан, колбек з props (onChange), який батько щоразу створює заново.
Обмеження:
- викликати лише з ефектів - не з обробників подій і не під час рендеру;
- не передавати в інші компоненти й хуки - функція оголошується поруч з ефектом, що її використовує;
- це не спосіб «приглушити» лінтер: якщо значення справді має перезапускати ефект, воно лишається в залежностях.
До появи хука це розв'язували через useRef з останнім значенням колбеку - useEffectEvent робить те саме офіційно й безпечніше для конкурентного рендерингу.