Модуль 19

Асинхронність і обробка помилок у React

Ось твій урок у стилі David Malan (CS50). Вмикай уяву: ми в аудиторії, ти на першому ряду, і ми розбираємо, як зробити твій React-додаток живим.


🎓 Урок: Асинхронність і обробка помилок у React

Привіт, світе! 👋 Ласкаво просимо на урок.

Сьогодні ми говоримо про те, що відрізняє "іграшковий" застосунок від професійного продукту. Ми говоримо про час, очікування і про те, що робити, коли все йде шкереберть.

1. 🔥 Вступ: Чому ми не можемо просто отримати дані?

Уявіть, що ви зайшли в улюблену кав’ярню. Ви підходите до баристи і замовляєте лате. Що відбувається далі?

Варіант А (Синхронний світ): Бариста завмирає. Ви завмираєте. Вся черга за вами завмирає. Час зупиняється. Бариста робить каву 3 хвилини. Тільки коли він віддає вам чашку, світ "розморожується", і наступна людина може хоча б привітатися.

Звучить як жах, правда? Але саме так працює код за замовчуванням: рядок за рядком, чекаючи завершення попереднього.

Варіант Б (Асинхронний світ — реальність): Ви замовляєте каву. Бариста киває, дає вам чек і каже: "Почекайте хвилинку". Ви відходите вбік, гортаєте стрічку в телефоні. Бариста приймає наступне замовлення. Процес іде паралельно. Коли кава готова — вас кличуть.

Питання до вас: Якби Instagram працював за "Варіантом А", і сервер, де лежать фотки котиків, "затупив" на 2 секунди — що сталося б з вашим телефоном? Правильно. Він би завис намертво. Жодна кнопка не працювала б.

У React ми зобов’язані використовувати асинхронність ("Варіант Б"), щоб інтерфейс залишався чуйним, поки дані летять до нас через океан кабелів.


2. 🧠 Теоретична база: Що там "під капотом"?

Давайте заглянемо всередину.

React — це майстер відображення (rendering). Його робота — малювати те, що є у state (стані) прямо зараз. Але запит на сервер (fetch) — це побічний ефект (side effect). Це займає час. React не може чекати. Він малює інтерфейс миттєво.

⚙️ Механіка процесу (Логіка, а не синтаксис):

  1. Render 1: Компонент монтується. Даних ще немає. Що малюємо? (Наприклад, спінер 🌀 або порожнє місце).
  2. Effect: Після того, як React намалював спінер, спрацьовує useEffect. Він каже: "Ей, браузер, зганяй на сервер за даними!".
  3. Wait: Браузер відправляє запит. React у цей час спокійно живе своїм життям, кнопки натискаються.
  4. Response: Дані прийшли! Ми оновлюємо state.
  5. Render 2: Оскільки state змінився, React перемальовує компонент. Тепер у нас є дані — показуємо їх.

🔑 Ключові поняття:

  • fetch / axios — кур’єри, які біжать за даними.
  • Promise (Проміс) — це "обіцянка" кур’єра: "Я повернусь або з даними (resolve), або з помилкою (reject)".
  • async / await — синтаксичний цукор, щоб писати асинхронний код так, ніби він синхронний (читабельніше).
  • Стани UI: Ви повинні думати не тільки про дані, а про три стани:
    1. isLoading (вантажимось)
    2. data (успіх)
    3. error (щось зламалось)

Запам’ятайте: React не чекає. Ви не можете написати const data = fetch(...) і відразу вивести data. Змінна буде пуста. Ви повинні "покласти" дані в state, коли вони прийдуть.


3. 🧪 Приклади: Від "Hello World" до Реальності

Приклад 1: Наївна спроба (Як робити НЕ треба)

Подивіться на цей код. Що тут не так?

function UserProfile() {
  // ❌ ПОМИЛКА: Це синхронний код
  const data = fetch('https://api.example.com/user');

  return <div>Привіт, {data.name}</div>;
}

Чому це погано: fetch повертає Проміс (обіцянку), а не дані. React спробує вивести об’єкт Промісу і впаде з помилкою, або покаже пустоту.


Приклад 2: Класичний патерн (Як треба)

Давайте зробимо це правильно. Використаємо useEffect і три стани.

import { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true); // Починаємо із завантаження
  const [error, setError] = useState(null);

  useEffect(() => {
    // Оголошуємо асинхронну функцію всередині ефекту
    const fetchData = async () => {
      try {
        // Скидаємо помилки перед новим запитом
        setError(null);

        const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

        // Перевірка на HTTP помилки (наприклад, 404)
        if (!response.ok) {
          throw new Error('Упс! Не вдалося знайти користувача');
        }

        const result = await response.json();
        setUser(result); // Зберігаємо дані
      } catch (err) {
        setError(err.message); // Ловимо помилку
      } finally {
        setLoading(false); // Завжди вимикаємо спінер в кінці
      }
    };

    fetchData();
  }, []); // [] означає "виконай один раз при запуску"

  // Рендеринг на основі стану
  if (loading) return <h2>🌀 Завантаження...</h2>;
  if (error) return <h2 style={{color: 'red'}}>🚨 Помилка: {error}</h2>;

  return (
    <div style={{border: '1px solid #ccc', padding: '20px'}}>
      <h1>👤 {user.name}</h1>
      <p>Email: {user.email}</p>
      <p>Місто: {user.address.city}</p>
    </div>
  );
}

Аналіз: 1. Ми почали з loading: true. Користувач одразу бачить спінер. 2. try...catch — це наш бронежилет. Якщо інтернет зникне або сервер впаде, додаток не "крашнеться", а покаже красиве повідомлення про помилку. 3. finally гарантує, що спінер зникне в будь-якому разі.


4. 🛠 Практична частина

Прийшов час забруднити руки кодом! Відкривайте редактор (VS Code або CodeSandbox).

Завдання 1: Copy-Paste та Усвідомлення Скопіюйте Приклад 2 у свій проєкт. Запустіть. Переконайтеся, що бачите дані користувача "Leanne Graham" (це стандартний юзер з JSONPlaceholder).

Завдання 2: "Зламай мене повністю" 😈 Змініть URL у fetch на 'https://jsonplaceholder.typicode.com/users/9999'. Що ви очікуєте побачити? Сервер поверне 404. Чи спрацює наша обробка помилок? Перевірте, чи з’явиться червоний текст.

Завдання 3: Список замість одного Змініть URL на 'https://jsonplaceholder.typicode.com/users' (без /1). Тепер сервер поверне масив із 10 користувачів. Ваша задача: Перепишіть return (JSX), щоб вивести список імен через user.map(...). (Підказка: не забудьте про унікальний key для кожного елемента списку!)

Завдання 4: Міні-кейс "Кнопка оновлення" Додайте кнопку "Оновити дані" під списком. При натисканні вона має знову запускати завантаження даних (можливо, випадкового користувача). Підказка: Вам потрібно винести логіку fetchData або додати змінну-тригер у масив залежностей useEffect.

Завдання 5: А що, якщо... Користувач має повільний інтернет. Він натиснув "Завантажити", побачив спінер, втомився чекати і перейшов на іншу сторінку. А через 5 секунд дані прийшли. React спробує оновити стейт компонента, якого вже не існує. Це називається "Memory Leak". Задача (із зірочкою): Погугліть "React useEffect cleanup function abort controller" і спробуйте додати скасування запиту.


5. 💡 Мислення як у розробника

Як відрізнити новачка (Junior) від профі (Senior) у цій темі?

1. Новачок ігнорує помилки. Він пише тільки "щасливий шлях" (Happy Path). Якщо сервер впав — у новачка білий екран. Профі знає: помилки — це частина норми. Сервери падають, Wi-Fi зникає. Інтерфейс має бути готовим до цього.

2. Новачок забуває про Loading State. Він залишає старі дані на екрані або показує пусті поля, поки нові вантажаться. Це плутає користувача. Профі завжди дає зворотний зв'язок (Skeleton screens, спінери, прогрес-бари).

3. Профі думає про UX (User Experience). Замість простого alert('Error'), профі зробить кнопку "Спробувати ще раз".

Порада: Завжди тестуйте свій додаток із вкладкою Network у DevTools, увімкнувши режим "Slow 3G". Ви здивуєтесь, як боляче користуватися вашим сайтом без індикаторів завантаження.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? Ми навчили наш React-додаток спілкуватися із зовнішнім світом, не перетворюючи інтерфейс на заморожену статую.

Ми вміємо: * ✅ Робити запити до API через fetch. * ✅ Обробляти стан очікування (isLoading). * ✅ Ловити та показувати помилки (try/catch). * ✅ Розуміти, чому useEffect тут головний.

Що далі? Зараз ми вантажимо дані в кожному компоненті окремо. А що, якщо ці дані (наприклад, профіль юзера) потрібні в усьому додатку? Невже ми будемо робити fetch на кожній сторінці? Ні!

На наступному уроці ми розберемо Context API та Custom Hooks — інструменти, які дозволять нам написати логіку один раз і використовувати її всюди.

А поки що — це був CS50... тобто, урок з React. Кодьте із задоволенням! 🚀