nmk

Лабораторне заняття №17 (2 години). Синхронізація даних із зовнішніми джерелами.

Мета

Перенести логіку отримання даних з FakeStoreAPI (яку ми робили у ПР №13 на чистому JS) у React-компонент. Опанувати хук useEffect для виконання побічних ефектів (Side Effects) після рендерингу компонента, а також обробку станів завантаження (loading) та помилок (error).

План

  1. Створення станів [products, setProducts], [isLoading, setIsLoading], [error, setError].
  2. Використання хука useEffect для ініціалізації завантаження при першому рендері.
  3. Написання асинхронної функції fetchData всередині useEffect.
  4. Обробка відповідей і заміна фіктивних товарів на реальні з сервера.
  5. Умовний рендеринг: відображення спінера під час завантаження або повідомлення про помилку.

Хід роботи

Увага: Продовжуємо роботу у файлі App.jsx (або ProductsList.jsx, якщо ви винесли його окремо). Видаляємо наш хардкодний масив mockProducts.

  1. Ініціалізація станів (State):
    • Імпортуйте useState та useEffect з React.
    • Створіть 3 незалежних стани для контролю над даними:

      import { useState, useEffect } from "react";
      import { ProductCard } from "./components/ProductCard";
      
      export function App() {
        const [products, setProducts] = useState([]); // Початковий стан - порожній масив
        const [isLoading, setIsLoading] = useState(true); // Спочатку ми у стані завантаження
        const [error, setError] = useState(null); // Поки що помилок немає
      
        // ... Інший код
      }
      
  2. Завантаження даних (useEffect):
    • Додайте хук useEffect, який виконається тільки 1 раз під час монтування компонента (порожній масив залежностей []).
    • Не можна робити саму функцію хука асинхронною (useEffect(async () => ...)). Замість цього створіть асинхронну функцію ВСЕРЕДИНІ хука і одразу її викличте.

      useEffect(() => {
        // Наша синка функція для завантаження
        const fetchProducts = async () => {
          setIsLoading(true); // Про всяк випадок переконуємося
          try {
            const response = await fetch(
              "https://fakestoreapi.com/products?limit=8",
            );
      
            // Кидаємо помилку, якщо щось пішло не так (наприклад, 404 або 500)
            if (!response.ok) {
              throw new Error(`Помилка сервера: ${response.status}`);
            }
      
            const data = await response.json();
      
            // Успіх! Оновлюємо стан масиву
            setProducts(data);
          } catch (err) {
            // Щось впало. Записуємо помилку в стан
            setError(err.message);
          } finally {
            // У будь-якому випадку (і при успіху, і при помилці) — вимикаємо спінер
            setIsLoading(false);
          }
        };
      
        fetchProducts(); // Викликаємо щойно створену функцію
      }, []); // <--- ЦЕЙ МАСИВ ОБОВ'ЯЗКОВИЙ (щоб не було нескінченного циклу)
      
  3. Умовний рендеринг (Conditional Rendering):
    • Додайте перевірки перед тим, як рендерити масив:

      return (
        <div className="app-container">
          <main className="page-layout">
            <h2>Популярні товари</h2>
      
            {/* 1. Показуємо повідомлення про помилку */}
            {error && <p className="error-text">Ой! Код впав: {error}</p>}
      
            {/* 2. Показуємо лоадер */}
            {isLoading && <div className="spinner">Завантаження...</div>}
      
            {/* 3. Показуємо сітку з товарами (якщо немає помилки і не завантажується) */}
            {!isLoading && !error && (
              <div className="products-grid">
                {products.map((product) => (
                  <ProductCard
                    key={product.id}
                    title={product.title}
                    price={product.price}
                    image={product.image}
                    /* Доведеться використовувати price через долари, або конвертувати */
                  />
                ))}
              </div>
            )}
          </main>
        </div>
      );
      
  4. Тестування:
    • Спробуйте змінити посилання fetch('https://fakesXstoreapi.com/products') на зламане і переконайтеся, що ви бачите повідомлення з тексту помилки (error && ...), а сайт не ламається в “білий екран смерті”.
    • Поверніть правильне посилання.
  5. Збереження (Commit & Push):
    • Виконайте git add . та git commit -m "Fetch products using useEffect from FakeStoreAPI".
    • Запушіть у свою гілку та злийте в main.

Результат

Новий React-додаток “TechShop” навчився самостійно робити запити до бекенду при запуску, показувати користувачеві зворотний зв’язок (індикатор завантаження) і безпечно відмальовувати реальні дані відразу після їх отримання.

Контрольні питання

  1. Що таке “Побічний ефект” (Side Effect) в React? Чому завантаження даних по мережі (або вручну звернення до document.getElementById) вважаються ефектами, а розрахунок суми чисел — ні?
  2. Що станеться, якщо у виклику useEffect(..., []) випадково забути передати другий аргумент (порожній масив)? Нащо він потрібен?
  3. Чому ми повинні створювати внутрішню async function fetchProducts і викликати її, замість того, щоб зробити сам колбек useEffect асинхронним? Що має повертати useEffect насправді згідно правил React (Cleanup function)?
  4. Поясніть концепцію “Умовного рендерингу” з використанням логічного ‘ТА’ (&&). Чому конструкція {isLoading && <p>Завантаження</p>} працює?
  5. Навіщо нам блок finally { setIsLoading(false) }, якщо ми в кінці try могли просто написати потрібні команди? Що буде, якщо ми вийдемо через catch без finally?