> Как правильно использовать useState и useEffect в кастомном хуке для хранения данных и ошибок (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Консорциум кодекс

Стек: React

> Пример ответа

Короткий ответ

Для хранения данных и ошибок в кастомном хуке используйте два отдельных useState: const [data, setData] = useState(null) и const [error, setError] = useState(null). useEffect выполняет асинхронный запрос, обновляя state через setData при успехе или setError при ошибке. Обязательно добавляйте cleanup-функцию для отмены запроса при размонтировании, чтобы избежать утечек памяти и race conditions.

Подробное объяснение

Кастомный хук для работы с данными должен учитывать жизненный цикл компонента и возможные состояния: загрузка, успех, ошибка. Использование отдельных useState для данных и ошибок даёт чёткое разделение ответственности и упрощает типизацию.

Ключевые моменты:

  • Разделение state: data и error хранятся отдельно, что позволяет компоненту гибко обрабатывать каждое состояние
  • Управление загрузкой: добавьте третий state loading для индикации процесса
  • Cleanup в useEffect: возвращайте функцию, которая отменяет запрос (через AbortController или флаг), чтобы предотвратить обновление state после размонтирования
  • Зависимости: передавайте в useEffect массив зависимостей, чтобы перезапрашивать данные при изменении параметров
  • Обработка ошибок: используйте try/catch внутри асинхронной функции, устанавливая error при любом исключении

Такой подход соответствует принципам React и позволяет переиспользовать логику запросов без дублирования кода.

На практике

При реализации хука следуйте этим правилам:

  • Всегда инициализируйте state с явным типом: useState<Data | null>(null)
  • Используйте AbortController для отмены запросов: const controller = new AbortController()
  • Не вызывайте setState после размонтирования - проверяйте флаг isMounted или используйте AbortController
  • Для зависимостей передавайте только примитивы или стабильные ссылки (через useMemo/useCallback)
  • Возвращайте из хука объект с полями data, error, loading и, опционально, refetch для повторного запроса

Пример кода

TYPESCRIPT
import { useState, useEffect, useCallback } from 'react';
interface UseFetchResult<T> {
data: T | null;
error: Error | null;
loading: boolean;
refetch: () => void;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const fetchData = useCallback(async (signal: AbortSignal) => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (!signal.aborted) {
setData(result);
}
} catch (err) {
if (!signal.aborted) {
setError(err instanceof Error ? err : new Error('Unknown error'));
}
} finally {
if (!signal.aborted) {
setLoading(false);
}
}
}, [url]);
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort();
}, [fetchData]);
const refetch = useCallback(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort();
}, [fetchData]);
return { data, error, loading, refetch };
}

Как отвечать на собеседовании

Начните с краткого описания задачи - хук для работы с асинхронными данными. Подчеркните важность управления жизненным циклом: cleanup предотвращает обновление state после размонтирования. Упомяните типизацию через generics и разделение состояний (data, error, loading). Приведите пример с AbortController как современный подход. Если спросят про альтернативы, скажите о React Query/SWR для production, но отметьте, что кастомный хук даёт полный контроль. Покажите понимание edge cases: повторные запросы при изменении url, обработка ошибок сети, корректная типизация.

Что проверяет интервьюер

  • Понимание lifecycle React-компонентов и эффектов
  • Умение работать с асинхронностью в React (race conditions, cleanup)
  • Знание TypeScript и типизации хуков
  • Способность проектировать переиспользуемые абстракции
  • Понимание trade-off между кастомным хуком и готовыми решениями
  • Внимание к деталям: обработка ошибок, индикация загрузки, refetch

Типичные ошибки

  • Игнорирование cleanup: setState после размонтирования вызывает warning и утечки
  • Отсутствие обработки ошибок: не обёрнутый в try/catch fetch приведёт к необработанному rejection
  • Неправильные зависимости: пропуск url в массиве зависимостей или включение нестабильных ссылок
  • Смешивание состояний: хранение data и error в одном объекте усложняет типизацию и обработку
  • Забывают про loading: без него компонент не может показать спиннер или скелетон
  • Использование async функции напрямую в useEffect без обёртки: это приводит к ошибке, так как useEffect ожидает синхронную функцию или cleanup

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью