Загружайте данные в эффекте, храните поиск и направление сортировки отдельно, а видимый список вычисляйте из исходных данных.
Реализуйте в одном 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. Для больших данных фильтрацию и пагинацию обычно переносят на сервер.