Домашнє завдання #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, і вона потрапить у твійcatchfinally— ідеальне місце ховати лоадер: виконується і після успіху, і після помилки- Подія
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 і переживає перезавантаження
Як здати
- Робота — у твоєму репозиторії курсу
my-web-course, у папціhw-16/ - Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — чекер бачить git-історію
- У папці —
README.md: 2-3 речення, що зроблено і як запустити - Скріншоти в
hw-16/screenshots/:desktop.png— сторінка зі списком карток на ширині ~1440pxmobile.png— на ширині ~390pxinteraction.png— стан loading або error (для error: DevTools → Network → Offline → перезавантаж)
- Надішли посилання на репозиторій (можна одразу на папку
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 для книг…). Патерн «лоадер → запит → дані → помилки» звідси поїде туди без змін.