Вивчай
Домашнє завдання #16 · Fetch API, async/await, обробка помилок, стани завантаження
100 балів+15 бонусintermediate

Домашнє завдання #16: Підключаємо продукт до світу — дані з API

Досі всі дані твого продукту жили прямо в коді або в localStorage. Сьогодні — переломний момент: сторінка вперше отримає дані з мережі. Уяви легенду: команда бекендерів твого продукту ще пише справжній сервер, а поки дала тобі тестовий API — щоб фронтенд не простоював. Такий підхід (розробка на mock-API) — абсолютно реальна практика в командах.

Тестовим API буде JSONPlaceholder — безкоштовний fake REST API, який віддає користувачів і пости. Так, це не фільми і не рецепти — але механіка (запит → очікування → картки → помилки) точно та сама, що знадобиться твоєму каталогу з реальним API у React-блоці.


Що ти вже маєш

  • Картки та стилі каталогу з ДЗ #14 — картки користувачів роби в тому ж стилі, тільки дані тепер приходять з мережі
  • Сучасний синтаксис з ДЗ #15 — тут він запрацює на повну

Файлова структура

hw-16/
├── index.html
├── style.css
├── script.js
└── screenshots/
    ├── desktop.png
    ├── mobile.png
    └── interaction.png

Специфікація

1. Завантаження та відображення користувачів

Ендпоінт: GET https://jsonplaceholder.typicode.com/users

Сценарій: користувач відкриває сторінку → бачить лоадер (спінер або текст «Завантаження…») → за мить лоадер зникає і з'являється сітка карток.

Кожна картка показує: ім'я, email, назву компанії (company.name) та місто (address.city).

┌─────────────────────┐  ┌─────────────────────┐
│ Leanne Graham       │  │ Ervin Howell        │
│ Sincere@april.biz   │  │ Shanna@melissa.tv   │
│ Romaguera-Crona     │  │ Deckow-Crist        │
│ Gwenborough         │  │ Wisokyburgh         │
└─────────────────────┘  └─────────────────────┘

2. Обробка помилок

Сценарій: сервер недоступний або відповів помилкою → користувач бачить зрозуміле повідомлення («Не вдалося завантажити дані. Спробуй пізніше») — а не вічний спінер і не порожню сторінку.

  • Лоадер зникає в будь-якому випадку — і при успіху, і при помилці
  • Врахуй: fetch не вважає відповідь 404 чи 500 помилкою — перевіряй статус відповіді сам

Як побачити помилку на живому API: DevTools → вкладка Network → перемкни throttling у Offline → перезавантаж сторінку.

3. Пости користувача

Ендпоінт: GET https://jsonplaceholder.typicode.com/users/{id}/posts (id підставляється)

Сценарій: клік на картку користувача → завантажуються його пости → вони з'являються під картками або в модалці (модалка в тебе вже є з ДЗ #12!).

  • Кожен пост: заголовок + тіло, обрізане до 100 символів з трьома крапками
  • Під час завантаження постів теж показується лоадер, помилки теж обробляються

4. Пошук

Сценарій: користувач друкує у полі пошуку → список карток фільтрується за ім'ям на кожне натискання клавіші, без кнопки «Шукати» і без нових запитів до сервера (фільтрація по вже завантаженому масиву, без урахування регістру).

  • Порожнє поле → знову видно всіх
  • Якщо збігів немає — покажи «Нічого не знайдено», а не порожнечу

5. Технічні вимоги

  • Усі запити — через fetch з async/await; ланцюжків .then() у коді немає
  • Помилки ловляться через try/catch
  • Код розбитий на невеликі функції з осмисленими іменами: окремо запит даних, окремо рендеринг, окремо фільтрація (як саме назвеш — вирішуй сам)

Підказки

Рівень 1: напрямок думки
  • Потік один і той самий для будь-якого запиту: показати лоадер → зробити запит → сховати лоадер → намалювати дані або помилку. Подумай, де в цьому ланцюжку місце для try, catch і коду, що виконується завжди
  • Завантажених користувачів збережи в змінну-масив — пошук фільтрує саме її, а не смикає сервер
  • Рендеринг карток — окрема функція, що приймає масив. Тоді і перше відображення, і результати пошуку — один і той самий виклик
Рівень 2: які інструменти використати
  • const response = await fetch(url), потім await response.json()
  • response.ok (або response.status) — перевірка, що відповідь не 4xx/5xx; якщо ні — кидай помилку через throw, і вона потрапить у твій catch
  • finally — ідеальне місце ховати лоадер: виконується і після успіху, і після помилки
  • Подія input на полі пошуку + toLowerCase() + includes() для фільтрації
  • text.slice(0, 100) для обрізки тіла поста
  • Лоадер — просто елемент, якому додають/знімають клас hidden
Рівень 3: псевдокод
функція loadUsers:
  показати лоадер
  try:
    відповідь = await fetch(url користувачів)
    якщо відповідь не ok → кинути помилку
    users = await відповідь.json()
    намалювати картки з users
  catch:
    показати повідомлення про помилку
  finally:
    сховати лоадер

клік по контейнеру карток (делегування, як у ДЗ #14):
  знайти картку через closest, взяти id користувача з dataset
  аналогічний try/catch-запит постів → намалювати пости

подія input на полі пошуку:
  запит = значення поля у нижньому регістрі
  відфільтрувати users за імʼям
  намалювати картки з відфільтрованого масиву (або "Нічого не знайдено")

Бонус (+15)

Реалізуй одну або кілька з цих можливостей:

  • Пагінація постів: по 5 на сторінку, кнопки «Далі / Назад» або «Показати ще»
  • Створення поста: форма + POST-запит на /posts (JSONPlaceholder симулює створення і повертає об'єкт з id) → новий пост з'являється у списку
  • Улюблені: зірочка на картці користувача, список улюблених зберігається в localStorage і переживає перезавантаження

Як здати

  1. Робота — у твоєму репозиторії курсу my-web-course, у папці hw-16/
  2. Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — чекер бачить git-історію
  3. У папці — README.md: 2-3 речення, що зроблено і як запустити
  4. Скріншоти в hw-16/screenshots/:
    • desktop.png — сторінка зі списком карток на ширині ~1440px
    • mobile.png — на ширині ~390px
    • interaction.png — стан loading або error (для error: DevTools → Network → Offline → перезавантаж)
  5. Надішли посилання на репозиторій (можна одразу на папку hw-16)

Критерії оцінки

КритерійТипБали
Користувачі завантажуються при відкритті сторінки і відображаються картками (ім'я, email, компанія, місто)[поведінка]20
Лоадер видимий під час запиту і гарантовано зникає після відповіді (успіх або помилка)[поведінка]10
Помилки оброблено: перевірка статусу відповіді + зрозуміле повідомлення користувачу[поведінка]15
Клік на картку завантажує і показує пости користувача (заголовок + тіло до 100 символів)[поведінка]20
Пошук фільтрує картки в реальному часі на клієнті, без нових запитів; стан «нічого не знайдено»[поведінка]10
async/await + try/catch у всіх запитах, жодного .then(); код розбитий на функції[код]15
desktop.png: сітка карток користувачів; mobile.png без поламаної верстки[скрін]5
interaction.png: видимий стан loading або error[скрін]5
Бонус: пагінація / POST-запит / улюблені в localStorage[поведінка]+15

Що далі

Це була генеральна репетиція. У ДЗ #21 ти будеш малювати такі ж картки вже в React, а в ДЗ #23 твій каталог нарешті підключиться до справжнього API своєї теми (TMDB для фільмів, OpenLibrary для книг…). Патерн «лоадер → запит → дані → помилки» звідси поїде туди без змін.

Пов'язані уроки