Кастомные хуки

Учимся выносить логику с состоянием в собственные хуки, чтобы переиспользовать её между компонентами.

Кастомный хук — обычная функция, имя которой начинается с use и которая внутри использует встроенные хуки React, чтобы инкапсулировать переиспользуемую логику.

Зачем нужны кастомные хуки

Компоненты переиспользуют разметку. А как переиспользовать логику — например, «загрузить данные», «следить за шириной окна», «работать с localStorage»? Копировать useState + useEffect из компонента в компонент — плохо. Эту логику выносят в кастомный хук.

Пример: useToggle

Часто нужно переключаемое булево состояние (модалка открыта/закрыта). Вынесем это:

import { useState } from "react";

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = () => setValue((v) => !v);
  return [value, toggle];
}

Теперь любой компонент использует это в одну строку:

function Modal() {
  const [isOpen, toggle] = useToggle(false);
  return (
    <div>
      <button onClick={toggle}>
        {isOpen ? "Закрыть" : "Открыть"}
      </button>
      {isOpen && <p>Содержимое окна</p>}
    </div>
  );
}

Пример: useFetch

Логику загрузки из раздела про эффекты тоже легко обобщить в хук:

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

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then((r) => r.json())
      .then(setData)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading };
}

И применение в любом компоненте:

function Profile({ id }) {
  const { data, loading } = useFetch(`/api/users/${id}`);
  if (loading) return <p>Загрузка...</p>;
  return <h1>{data.name}</h1>;
}

Два правила кастомных хуков

  1. Имя начинается с use — по нему React и линтер понимают, что внутри работают хуки, и проверяют правила.
  2. Внутри действуют те же правила хуков — вызывать встроенные хуки только на верхнем уровне функции.

Важно: состояние не разделяется

Распространённое заблуждение: будто два компонента, использующих один хук, делят состояние. Это не так — каждый вызов хука создаёт собственное независимое состояние. Хук переиспользует логику, а не данные. Если два компонента вызвали useToggle, у каждого свой isOpen.

Итог

  • Кастомный хук выносит логику с состоянием/эффектами в функцию с именем на use.
  • Внутри действуют обычные правила хуков; хук возвращает то, что нужно компоненту (значения, функции).
  • Каждый вызов хука имеет своё независимое состояние — переиспользуется логика, а не данные.
Проверьте себя
1. Что обязательно для имени кастомного хука?
AЗаканчиваться на Hook
BНачинаться с use
CСодержать слово custom
DБыть с заглавной буквы
2. Что переиспользует кастомный хук между компонентами?
AСамо состояние (данные)
BЛогику; состояние у каждого вызова своё, независимое
CРазметку JSX
DDOM-узлы
3. Зачем выносить логику в кастомный хук?
AЧтобы ускорить рендер
BЧтобы переиспользовать повторяющуюся логику с состоянием/эффектами без копирования
CЧтобы обойти правила хуков
DЧтобы избавиться от JSX
Поддержать проект