React hooks для початківців: useState та useEffect

Кошеня вивчає React hooks біля ноутбука з кодом

Hooks дають функціональному компоненту пам’ять і можливість синхронізуватися із зовнішнім світом. На старті достатньо зрозуміти дві речі: useState зберігає стан між рендерами, а useEffect запускає побічну дію після рендеру.

Компонент і повторний рендер

Компонент — функція, яка описує частину інтерфейсу. Коли props або state змінюються, React знову викликає цю функцію. Локальна звичайна змінна не підходить для даних, які повинні пережити рендер і змінити екран.

useState: стан компонента

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>
    Натискань: {count}
  </button>;
}

count — поточне значення, setCount — функція оновлення. Не змінюйте state напряму. Для об’єкта створюйте нове значення: setUser({...user, name: value}).

Коли потрібне функціональне оновлення

Якщо наступний стан залежить від попереднього, безпечніше написати setCount(current => current + 1). Це особливо важливо для кількох оновлень в одній події.

useEffect: dependencies, запит до API та cleanup

Effect синхронізує component із зовнішньою системою: API, timer, browser event або subscription. Logic, яку природно виконати після натискання кнопки, краще залишити безпосередньо в event handler. Похідне значення, яке можна обчислити під час render, також не потребує effect.

Без dependency array effect запускається після кожного render; [] означає синхронізацію під час mount; [courseId] — повторну синхронізацію після зміни courseId. Це не обіцянка «рівно одного запуску»: у development React Strict Mode може виконати додатковий setup/cleanup cycle, щоб виявити небезпечні effects.

function Course({ courseId }) {
  const [course, setCourse] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    setError('');

    fetch(`/api/courses/${courseId}`, { signal: controller.signal })
      .then(response => {
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        return response.json();
      })
      .then(setCourse)
      .catch(error => {
        if (error.name !== 'AbortError') setError('Не вдалося завантажити курс');
      })
      .finally(() => {
        if (!controller.signal.aborted) setLoading(false);
      });

    return () => controller.abort();
  }, [courseId]);
}

Cleanup скасовує попередній request, коли component зникає або courseId змінюється. Dependencies не приховуйте disable-коментарем: перевірте, чи effect справді потрібен і чи всі використані зовнішні значення враховані.

Типові помилки

  • Виклик setter під час рендеру.
  • Мутація масиву через push.
  • Копіювання props у state без причини.
  • Запит до API без loading, error і cleanup.
  • Effect для простого фільтрування масиву.

Невелика практика

Створіть список нотаток: поле вводу, додавання, видалення і лічильник. Збережіть список у localStorage через effect. Потім поясніть, які дані є state, а які можна обчислити.

Derived values не завжди є state

Якщо filtered list можна отримати з масиву та search query під час рендеру, не зберігайте його окремо. Два пов’язані state легко розсинхронізувати. Залишайте мінімальне джерело правди, а похідні значення обчислюйте.

const visibleTasks = tasks.filter(task =>
  task.title.toLowerCase().includes(query.toLowerCase())
);

Checklist review компонента

  • State мінімальний і має зрозумілу початкову форму.
  • Setter не викликається під час рендеру.
  • Lists мають стабільні keys.
  • Effect працює із зовнішньою системою.
  • Усі dependencies пояснювані.
  • Subscription має cleanup.

Коли ці правила зрозумілі, hooks перестають бути набором рецептів і стають передбачуваним способом керувати життєвим циклом компонента.

Наступний крок

Якщо мовна база ще нестійка, почніть зі статті JavaScript після основ: коли переходити до React. Практику компонентів, API та hooks продовжує Frontend Development: JavaScript & React.

Хочете перейти від читання до практики?

Оберіть курс або спробуйте безкоштовне перше заняття.