React‑приложение со списком персонажей

Храните поиск и страницу в состоянии, загружайте данные с отменой устаревшего запроса. Сортируйте полученную страницу, а подробности показывайте в диалоге.

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

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

Храните поиск и страницу в состоянии, загружайте данные с отменой устаревшего запроса. Сортируйте полученную страницу, а подробности показывайте в диалоге.

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

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

Условие

Разработать React‑приложение, которое получает перечень персонажей из публичного API «Rick and Morty». В интерфейсе необходимо реализовать:

  • поле поиска и возможность сортировки по имени и статусу;
  • постраничный вывод данных;
  • для каждого элемента списка показывать всплывающее окно с подробной информацией о персонаже.

Константа с адресом API:

API_LINK = "https://rickandmortyapi.com/api/character";

Базовый шаблон приложения (не менять):

 import "./styles.css";

export default function App() {
    return (
    <div className="App">
    <h1>Hello CodeSandbox</h1>
    <h2>hello</h2>
    </div>
);

Ответ

Храните поиск и страницу в состоянии, загружайте данные с отменой устаревшего запроса. Сортируйте полученную страницу, а подробности показывайте в диалоге.

API поддерживает name и page, но не глобальную сортировку по имени или статусу. Ниже сортируется текущая страница; глобальный порядок потребует отдельного слоя данных. Сохраните обёртку и заголовки App, добавьте внутрь <Characters /> и недостающую закрывающую скобку функции.

import { useEffect, useRef, useState } from 'react';
const API_LINK = 'https://rickandmortyapi.com/api/character';
function Characters() {
  const [query, setQuery] = useState({ name: '', page: 1 });
  const [sort, setSort] = useState('name');
  const [data, setData] = useState({ results: [], pages: 0 });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [selected, setSelected] = useState(null);
  const dialog = useRef(null);
  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    setError('');
    const timer = setTimeout(async () => {
      try {
        const url = new URL(API_LINK);
        url.searchParams.set('name', query.name);
        url.searchParams.set('page', String(query.page));
        const response = await fetch(url, { signal: controller.signal });
        if (response.status === 404) {
          if (!controller.signal.aborted) setData({ results: [], pages: 0 });
          return;
        }
        if (!response.ok) throw new Error('Не удалось загрузить персонажей');
        const payload = await response.json();
        if (!controller.signal.aborted) {
          setData({ results: payload.results, pages: payload.info.pages });
        }
      } catch (e) {
        if (!controller.signal.aborted) setError(e.message);
      } finally {
        if (!controller.signal.aborted) setLoading(false);
      }
    }, 250);
    return () => { clearTimeout(timer); controller.abort(); };
  }, [query]);
  useEffect(() => {
    if (selected && !dialog.current.open) dialog.current.showModal();
  }, [selected]);
  const items = [...data.results].sort((a, b) => a[sort].localeCompare(b[sort]));
  return <section>
    <label>Поиск <input value={query.name} onChange={e =>
      setQuery({ name: e.target.value, page: 1 })} /></label>
    <label>Сортировка <select value={sort} onChange={e => setSort(e.target.value)}>
      <option value="name">Имя</option><option value="status">Статус</option>
    </select></label>
    {loading ? <p role="status">Загрузка…</p> : error ? <p role="alert">{error}</p> : <>
      {!items.length && <p>Ничего не найдено</p>}
      <ul>{items.map(item => <li key={item.id}>
        <button onClick={() => setSelected(item)}>{item.name} — {item.status}</button>
      </li>)}</ul>
    </>}
    <button disabled={loading || query.page <= 1} onClick={() =>
      setQuery(q => ({ ...q, page: q.page - 1 }))}>Назад</button>
    <span> Страница {query.page} / {data.pages || 1} </span>
    <button disabled={loading || query.page >= data.pages} onClick={() =>
      setQuery(q => ({ ...q, page: q.page + 1 }))}>Вперёд</button>
    <dialog ref={dialog} aria-labelledby="character-name" onClose={() => setSelected(null)}>
      {selected && <><h2 id="character-name">{selected.name}</h2>
        <img src={selected.image} alt={selected.name} width="160" />
        <p>{selected.status} · {selected.species} · {selected.gender}</p>
        <p>Происхождение: {selected.origin.name}</p>
        <p>Локация: {selected.location.name}</p></>}
      <button onClick={() => dialog.current.close()}>Закрыть</button>
    </dialog>
  </section>;
}

Источник: rickandmortyapi.com.

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

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

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

Подробнее