
useEffect — почему его использование стало признаком плохого кода
Помню как начинал с React. useEffect казался универсальным решением для всего. Нужно загрузить данные? useEffect. Синхронизировать стейт? useEffect. Анимировать что-то? Конечно useEffect.
В 2026 году такой подход - красный флаг при код-ревью.
Что пошло не так
useEffect изначально создавался для одной конкретной задачи - синхронизации с внешними системами. Браузерные API, сторонние библиотеки, подписки на события. Это его законная территория.
Но разработчики превратили его в catch-all решение для любой логики которая должна «запуститься после рендера». Результат - цепочки эффектов где один обновляет стейт, это запускает другой эффект, который снова обновляет стейт. Приложение становится медленным и непредсказуемым.
На dev.to разработчик написал прямо: «Если я вижу в PR 15 useEffect в одном компоненте - я знаю что сейчас найду баг».
Три самых частых антипаттерна
1. Производный стейт через useEffect
// ✗ Плохо - лишний рендер, лишний эффект
const [firstName, setFirstName] = useState('Максим')
const [lastName, setLastName] = useState('Исаев')
const [fullName, setFullName] = useState('')
useEffect(() => {
setFullName(`${firstName} ${lastName}`)
}, [firstName, lastName])
// ✓ Хорошо - вычисляем прямо при рендере
const fullName = `${firstName} ${lastName}`
Если значение можно вычислить из существующего стейта или пропсов - никакой useEffect не нужен. Просто переменная.
2. Фетчинг данных вручную
// ✗ Плохо - нет обработки race conditions, нет кэша, нет загрузки
useEffect(() => {
fetch('/api/notes')
.then(r => r.json())
.then(setNotes)
}, [])
// ✓ Хорошо - TanStack Query решает всё это за тебя
const { data: notes, isLoading } = useQuery({
queryKey: ['notes'],
queryFn: () => fetch('/api/notes').then(r => r.json()),
})
Ручной фетч в useEffect без cleanup функции - это потенциальный race condition. Если компонент размонтируется до получения ответа - обновление стейта произойдёт на несуществующем компоненте.
3. Реакция на события через useEffect
// ✗ Плохо - useEffect для обработки клика
const [submitted, setSubmitted] = useState(false)
useEffect(() => {
if (submitted) {
saveNote(content)
}
}, [submitted])
// ✓ Хорошо - логика в обработчике события
const handleSubmit = () => {
saveNote(content)
}
Если что-то происходит потому что пользователь нажал кнопку - логика должна быть в обработчике события, не в эффекте.
Когда useEffect всё-таки нужен
useEffect не плохой хук - он просто часто используется не по назначению. Его законные случаи:
- Подписки на внешние события (WebSocket, localStorage события)
- Интеграция со сторонними библиотеками (карты, графики)
- Браузерные API которые недоступны при SSR
Официальная документация React говорит прямо: useEffect - это escape hatch, аварийный выход из реактивной модели React. Не главный вход.
Мысленный тест перед написанием useEffect
Прежде чем писать useEffect - ответь на три вопроса:
- Могу ли я вычислить это из существующего стейта или пропсов? → Просто переменная
- Это происходит потому что пользователь что-то сделал? → Обработчик события
- Это синхронизация с внешней системой? →
useEffectуместен
Цель хорошего React разработчика - не писать больше хуков. А удалять их. 🎯

Комментарии