> Как реализовать React компонент с текстовым полем для запроса к API, отображением результатов, индикатором загрузки и обработкой ошибок? (React)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Т-Банк, IT-One, Сбер
Стек: React
> Пример ответа
Короткий ответ
Реализуем controlled input с debounce, хук useEffect для отправки запроса при изменении значения, AbortController для отмены предыдущих запросов и useReducer для управления состояниями (loading, error, data). Индикатор загрузки показываем через спиннер, ошибки - через отдельный блок с возможностью повторного запроса. Для оптимизации используем useCallback и useMemo.
Подробное объяснение
Компонент должен решать несколько задач: управление вводом, отправка запросов с задержкой, обработка race condition, отображение состояний загрузки и ошибок. Основные механизмы:
- Controlled input - значение хранится в state, onChange обновляет его
- Debounce - задержка перед отправкой запроса (300-500ms), реализуется через
setTimeoutвuseEffect - AbortController - отменяет предыдущий запрос при новом вводе, предотвращая устаревшие ответы
- useReducer - управляет состояниями: idle, loading, success, error. Это чище, чем несколько useState
- Обработка ошибок - catch блок с установкой error state, отображение сообщения и кнопки retry
- Отмена при unmount - cleanup функция в useEffect отменяет запрос и таймер
Для production стоит добавить кэширование результатов (через Map или React Query), обработку пустого результата и доступность (aria-live для screen readers).
На практике
В реальных проектах часто используют готовые решения: React Query (TanStack Query) или SWR, которые предоставляют встроенные механизмы кэширования, ревалидации и дедупликации запросов. Но для понимания основ важно уметь реализовать вручную.
Ключевые моменты реализации:
- Использовать
useRefдля хранения AbortController, чтобы не создавать лишние ререндеры - Не забывать очищать таймер debounce при размонтировании
- Обрабатывать ошибки сети отдельно от ошибок API (статус 4xx vs 5xx)
- Показывать скелетон или спиннер, но не блокировать интерфейс
- Для списка результатов использовать виртуализацию (react-window) при большом количестве
Пример кода
JSXimport { useState, useEffect, useReducer, useRef, useCallback } from 'react';const initialState = {status: 'idle',data: [],error: null};function reducer(state, action) {switch (action.type) {case 'LOADING':return { ...state, status: 'loading', error: null };case 'SUCCESS':return { status: 'success', data: action.payload, error: null };case 'ERROR':return { status: 'error', data: [], error: action.payload };case 'RESET':return initialState;default:return state;}}function SearchComponent() {const [query, setQuery] = useState('');const [state, dispatch] = useReducer(reducer, initialState);const abortRef = useRef(null);const timerRef = useRef(null);const searchApi = useCallback(async (searchTerm, signal) => {const response = await fetch(`/api/search?q=${searchTerm}`, { signal });if (!response.ok) throw new Error(`HTTP ${response.status}`);return response.json();}, []);useEffect(() => {if (!query.trim()) {dispatch({ type: 'RESET' });return;}// Отмена предыдущего запросаif (abortRef.current) abortRef.current.abort();const controller = new AbortController();abortRef.current = controller;// DebounceclearTimeout(timerRef.current);timerRef.current = setTimeout(async () => {dispatch({ type: 'LOADING' });try {const results = await searchApi(query, controller.signal);dispatch({ type: 'SUCCESS', payload: results });} catch (err) {if (err.name !== 'AbortError') {dispatch({ type: 'ERROR', payload: err.message });}}}, 300);return () => {clearTimeout(timerRef.current);controller.abort();};}, [query, searchApi]);const handleRetry = () => {setQuery(prev => prev + ' '); // триггер повторного запроса};return (<div><inputtype="text"value={query}onChange={(e) => setQuery(e.target.value)}placeholder="Поиск..."aria-label="Поиск"/>{state.status === 'loading' && <div>Загрузка...</div>}{state.status === 'error' && (<div role="alert">Ошибка: {state.error}<button onClick={handleRetry}>Повторить</button></div>)}{state.status === 'success' && (<ul>{state.data.map(item => (<li key={item.id}>{item.name}</li>))}</ul>)}</div>);}
Как отвечать на собеседовании
Начни с ключевых проблем: race condition, debounce, управление состояниями. Объясни, почему используешь useReducer вместо нескольких useState - это показывает понимание управления сложным состоянием. Упомяни AbortController как решение для отмены запросов. Если спросят про оптимизацию - расскажи про мемоизацию callback'ов и значений, виртуализацию списка.
Для senior позиции важно показать понимание edge cases: что будет при быстром вводе, при размонтировании компонента, при ошибках сети. Хорошо добавить про accessibility (aria-live, role="alert") и про то, как это масштабируется на несколько полей поиска на странице.
Что проверяет интервьюер
- Понимание жизненного цикла React компонентов и эффектов
- Умение работать с асинхронностью и отменой запросов
- Знание паттернов управления состоянием (useReducer vs useState)
- Внимание к деталям: очистка таймеров, обработка ошибок, предотвращение утечек памяти
- Способность проектировать компоненты с учетом UX (индикаторы загрузки, обработка ошибок)
Типичные ошибки
- Не используют AbortController - при быстром вводе могут отобразиться устаревшие результаты
- Забывают очищать таймер debounce при unmount - вызывает setState на размонтированном компоненте
- Хранят результаты в одном useState с загрузкой и ошибкой - приводит к сложной логике обновления
- Не обрабатывают ошибки сети отдельно от ошибок API
- Используют setTimeout без cleanup функции в useEffect
- Не учитывают пустой ввод - отправляют запросы с пустой строкой
> Похожие задачи по frontend
Как работает жизненный цикл React-компонентов
В чем отличие useEffect от useLayoutEffect в React
Почему React работает быстро, как устроен виртуальный DOM и процесс обновления?
Будет ли работать React.memo если пропсы передаются в виде объекта, который меняется по ссылке
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью