Модуль 24

Кастомні хуки

Ось урок на тему "Кастомні хуки", створений у стилі CS50: енергійно, з аналогіями та акцентом на розуміння суті.


🎓 CS50: Кастомні хуки (Custom Hooks)

Привіт, друзі! Радий бачити вас.

Сьогодні ми поговоримо про магію... ні, не про Гоґвортс. Ми поговоримо про магію чистого коду в React.

1. 🔥 Вступ: Чому ми тут?

Уявіть, що ви шеф-кухар у величезному ресторані. Ви готуєте три страви: салат, суп і рагу. Для кожної з них вам потрібно нарізати цибулю.

Як ви це робите? 1. Берете ніж і дошку для салату -> ріжете. 2. Прибираєте все, дістаєте новий ніж і нову дошку для супу -> ріжете. 3. Знову все міняєте для рагу -> ріжете.

Звучить абсурдно, правда? У реальному житті у вас є навичка "різати цибулю". Ви просто застосовуєте цю навичку там, де вона потрібна, не винаходячи колесо щоразу.

У React ми часто робимо ту саму помилку. Ми пишемо код для завантаження даних у компоненті UserProfile. Потім ми копіюємо (Ctrl+C) той самий код useEffect і useState у компонент ProductList. Потім у NewsFeed.

Питання до вас:

Якщо ми знайдемо помилку в логіці завантаження, у скількох місцях нам доведеться її виправляти?

Правильно. У всіх трьох (або тридцяти). Це порушує золоте правило програмування: DRY (Don't Repeat Yourself).

Сьогодні ми навчимося "пакувати" нашу логіку в окремі інструменти — Кастомні Хуки. Без них ваш код перетвориться на спагеті, яке неможливо підтримувати.


2. 🧠 Що це таке і як це працює?

Давайте спростимо.

Кастомний хук — це звичайна JavaScript функція. Але у неї є дві суперсили: 1. Її назва обов'язково починається з use (наприклад, useFetch, useWindowSize). 2. Вона може викликати інші хуки (useState, useEffect тощо).

Як це працює "під капотом"?

React не знає, що ви винесли код в окрему функцію. Коли ви викликаєте свій useMyHook всередині компонента, React просто бачить: "Ага, цей компонент хоче використати useState і useEffect, які сховані всередині цієї функції".

Що треба запам'ятати залізно: * Кастомні хуки дозволяють перевикористовувати логіку стану (stateful logic), а не сам стан. * Кожен раз, коли ви використовуєте хук, він створює ізольований стейт. Це не глобальне сховище даних!

Аналогія: Уявіть, що кастомний хук — це рецепт у книзі. Якщо я готую за цим рецептом у себе вдома, а ви — у себе, ми використовуємо одну й ту ж інструкцію (логіку), але інгредієнти (дані/стан) у кожного свої. Мій пиріг не з'явиться у вашій духовці.


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

Приклад 1: Простий перемикач (useToggle)

У розробці ми постійно щось вмикаємо і вимикаємо: модальні вікна, меню, чекбокси, спойлери.

Зазвичай це виглядає так:

const [isModalOpen, setIsModalOpen] = useState(false);
const toggleModal = () => setIsModalOpen(!isModalOpen);

Нудно писати це 100 разів. Давайте зробимо хук!

Чого ми очікуємо? Функцію, яка поверне нам поточне значення (true/false) і функцію для перемикання.

// useToggle.js
import { useState } from 'react';

// 1. Назва починається з use
export function useToggle(initialValue = false) {
  // 2. Всередині звичайний стейт
  const [value, setValue] = useState(initialValue);

  // 3. Логіка перемикання
  const toggle = () => setValue((prev) => !prev);

  // 4. Повертаємо те, що потрібно компоненту
  return [value, toggle];
}

Як це використати?

// App.js
function App() {
  // Викликаємо як useState, але це НАШ хук!
  const [isMenuOpen, toggleMenu] = useToggle(false);

  return (
    <button onClick={toggleMenu}>
      {isMenuOpen ? "Закрити меню" : "Відкрити меню"}
    </button>
  );
}

Приклад 2: Реальний світ (useFetch)

Це класика. Майже кожен додаток тягне дані з сервера. Нам потрібно: 1. Зберігати дані (data). 2. Знати, чи йде завантаження (loading). 3. Ловити помилки (error).

Давайте запакуємо це.

import { useState, useEffect } from "react";

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Скидаємо стани при зміні URL
    setLoading(true);
    setError(null);

    fetch(url)
      .then((res) => {
        if (!res.ok) throw new Error("Щось пішло не так...");
        return res.json();
      })
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
  }, [url]); // Перезапускаємо, якщо URL змінився

  // Повертаємо об'єкт для зручності
  return { data, loading, error };
}

Використання в компоненті:

function UserProfile({ userId }) {
  // Дивіться, як чисто! Жодного useEffect у компоненті.
  const { data, loading, error } = useFetch(`https://api.site.com/users/${userId}`);

  if (loading) return <p>Завантаження...</p>;
  if (error) return <p>Помилка: {error}</p>;

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

Бачите? Компонент займається лише відображенням (UI), а хук — логікою завантаження. Розділення відповідальності в дії!


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

Прийшов час забруднити руки кодом. Відкрийте ваш редактор.

Завдання 1: Повторення Створіть файл useCounter.js. Реалізуйте хук, який зберігає число, і повертає: саме число, функцію increment (+1), decrement (-1) та reset (скидання до початкового значення).

Завдання 2: Адаптація useToggle Змініть наш хук useToggle з прикладу вище так, щоб функція toggle могла приймати конкретне значення. * Якщо викликаємо toggle() -> змінює true на false. * Якщо викликаємо toggle(true) -> примусово ставить true.

Завдання 3: Знайди помилку Чому цей код не працюватиме або порушує правила React?

function MyComponent({ isOnline }) {
  if (isOnline) {
    const [status, setStatus] = useMyCustomHook(); // 🚩
  }
  return <div>...</div>;
}

(Підказка: Згадайте "Rules of Hooks" про умовні оператори).

Завдання 4: Міні-кейс (useLocalStorage) Напишіть хук useLocalStorage(key, initialValue). Він має працювати як useState, але: 1. При ініціалізації читати значення з localStorage. 2. При зміні стейту — записувати нове значення в localStorage. Це дозволить даним "виживати" після перезавантаження сторінки.

Завдання 5: "А що, якщо..." У вас є два компоненти: Header і Footer. Обидва використовують useToggle. Якщо ви натиснете кнопку в Header, чи зміниться стан у Footer? Проведіть експеримент і поясніть результат.


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

Як відрізнити новачка від профі?

1. Новачок повертає JSX. Якщо ваш "хук" повертає <div>...</div>, це не хук. Це Компонент. Хуки повертають дані, функції, об'єкти, але не верстку.

2. Профі не поспішає абстрагувати. Не створюйте хук наперед. Спочатку напишіть код у компоненті. Якщо бачите, що цей код потрібен ще десь, або компонент став занадто "жирним" (fat component) — тільки тоді виносьте в хук.

3. Профі пам'ятає про залежності. Коли ви пишете useEffect всередині кастомного хука, будьте дуже уважні з масивом залежностей []. Якщо ваш хук приймає аргументи, вони майже напевно мають бути у масиві залежностей ефекту.


6. 🧩 Підсумок

Отже, що ми маємо в сухому залишку?

  1. Кастомні хуки — це спосіб вирізати складну логіку з компонента і вставити її в окрему функцію.
  2. Вони роблять код чистішим (менше рядків у компоненті).
  3. Вони роблять код багаторазовим (DRY).
  4. Вони НЕ ділять стан між компонентами (стан ізольований).

Тепер ви вмієте: Створювати власні інструменти для React, замість того щоб просто користуватися готовими молотками. Ви стали інженерами, а не просто будівельниками.

Що далі? Ми навчилися ізолювати логіку. Але як передати дані через 10 компонентів вниз, не передаючи їх вручну через пропси (props drilling)? На наступному уроці ми розберемо Context API — телепорт для ваших даних.

А поки що... це був CS50! 👋