Реализуйте в одном React-компоненте список пользователей с поиском по имени или email, сортировкой A–Z/Z–A, состояниями загрузки и пустого списка, а также асинхронной загрузкой данных.

Загружайте данные в эффекте, храните поиск и направление сортировки отдельно, а видимый список вычисляйте из исходных данных.

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

Что ответить на собеседовании

Загружайте данные в эффекте, храните поиск и направление сортировки отдельно, а видимый список вычисляйте из исходных данных.

Подробный разбор

Ответ с пояснениями

Загружайте данные в эффекте, храните поиск и направление сортировки отдельно, а видимый список вычисляйте из исходных данных.

Предполагается API, возвращающий массив с уникальными string id, name и email; url — доверенный адрес API. При смене URL старые данные не отображаются, а предыдущий запрос отменяется.

import { useEffect, useState } from 'react';
function Users({ url }) {
  const [query, setQuery] = useState('');
  const [desc, setDesc] = useState(false);
  const [state, setState] = useState(null);
  useEffect(() => {
    let active = true;
    const controller = new AbortController();
    setState({ url, status: 'loading', users: [] });
    (async () => {
      try {
        const response = await fetch(url, { signal: controller.signal });
        if (!response.ok) throw new Error('HTTP');
        const users = await response.json();
        if (!Array.isArray(users) || !users.every(u => u &&
          typeof u.id === 'string' && typeof u.name === 'string' &&
          typeof u.email === 'string')) throw new Error('Format');
        if (active) setState({ url, status: 'ready', users });
      } catch {
        if (active) setState({ url, status: 'error', users: [] });
      }
    })();
    return () => { active = false; controller.abort(); };
  }, [url]);
  if (!state || state.url !== url || state.status === 'loading')
    return <p role="status">Загрузка…</p>;
  if (state.status === 'error') return <p role="alert">Не удалось загрузить</p>;
  const q = query.trim().toLowerCase();
  const visible = state.users.filter(u =>
    u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
  ).sort((a, b) => (desc ? -1 : 1) * a.name.localeCompare(b.name));
  return <section>
    <input aria-label="Поиск по имени или email" value={query}
      onChange={e => setQuery(e.target.value)} />
    <button type="button" onClick={() => setDesc(v => !v)}>
      {desc ? 'Z–A' : 'A–Z'}
    </button>
    {visible.length ? <ul>{visible.map(u =>
      <li key={u.id}>{u.name} — {u.email}</li>
    )}</ul> : <p>Пользователи не найдены</p>}
  </section>;
}

filter создаёт новый массив, поэтому sort не меняет исходный state. Для больших данных фильтрацию и пагинацию обычно переносят на сервер.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее