Все заметки
useEffect — почему его использование стало признаком плохого кода
21 июля 2026 г.
2 мин
2

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 - ответь на три вопроса:

  1. Могу ли я вычислить это из существующего стейта или пропсов? → Просто переменная
  2. Это происходит потому что пользователь что-то сделал? → Обработчик события
  3. Это синхронизация с внешней системой? → useEffect уместен

Цель хорошего React разработчика - не писать больше хуков. А удалять их. 🎯