Хотите заказать веб-сайт? Связаться с нами

Хуки useDisclosure, useToggle, useCounter

Не каждое состояние интерфейса заслуживает написания собственного хука с нуля — булевый флаг «открыто/закрыто», переключатель темы или счётчик товаров в корзине встречаются в проектах десятками раз. Библиотека @mantine/hooks уже решила эти типовые задачи за вас. В этом уроке разберём три небольших, но незаменимых хука: useDisclosure, useToggle и useCounter.

Хуки useDisclosure, useToggle, useCounter

Хук useDisclosure: управляем видимостью

useDisclosure создан специально для состояния вида «показано/скрыто» — модальные окна, выпадающие панели, поповеры, аккордеоны.

Вместо того чтобы каждый раз писать свой useState и отдельные функции для открытия и закрытия, хук сразу возвращает готовый набор: текущее булево значение и объект с обработчиками.

import { useDisclosure } from '@mantine/hooks';
 
function Example() {
  const [opened, { open, close, toggle }] = useDisclosure(false);
 
  return (
    <div>
      <p>Текущее состояние: {opened ? 'открыто' : 'закрыто'}</p>
      <button onClick={open}>Открыть</button>
      <button onClick={close}>Закрыть</button>
      <button onClick={toggle}>Переключить</button>
    </div>
  );
}

Первый аргумент useDisclosure — начальное значение (по умолчанию false). Вторым аргументом можно передать объект с колбэками onOpen и onClose, которые сработают в момент изменения состояния — удобно, например, чтобы одновременно с открытием модалки отправить событие в аналитику.

useDisclosure на практике: модальное окно

Чаще всего useDisclosure используют в связке с компонентом Modal, который мы разбирали в прошлом уроке. Хук отвечает за логику, а Modal — только за отображение: ему достаточно передать текущее состояние и функцию закрытия.

import { Modal, Button } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
 
function ConfirmDeleteButton() {
  const [opened, { open, close }] = useDisclosure(false);
 
  return (
    <>
      <Modal opened={opened} onClose={close} title="Подтвердите удаление">
        <p>Вы уверены, что хотите удалить запись?</p>
        <Button color="red" onClick={close}>Удалить</Button>
      </Modal>
 
      <Button onClick={open}>Удалить запись</Button>
    </>
  );
}

Такой подход полностью убирает из компонента ручную работу с булевым состоянием: не нужно помнить, как называется сеттер и в какую сторону его вызывать — open, close и toggle говорят сами за себя.

Хук useToggle: переключение между значениями

useToggle шире, чем просто переключатель булевого флага. Он умеет циклически переключаться между произвольным набором значений — например, между темами light и dark, вкладками или статусами сортировки asc и desc.

import { useToggle } from '@mantine/hooks';
 
function ThemeSwitcher() {
  const [theme, toggleTheme] = useToggle(['light', 'dark']);
 
  return (
    <button onClick={() => toggleTheme()}>
      Текущая тема: {theme}
    </button>
  );
}

Если вызвать функцию без аргументов, значение сдвинется к следующему по списку (а после последнего — снова к первому). А если передать конкретное значение явно, например toggleTheme('dark'), хук перепрыгнет сразу к нему, минуя очередь переключения.

Не пишите вручную const [flag, setFlag] = useState(false) с последующим setFlag((v) => !v) для простого переключателя — замените это на const [flag, toggleFlag] = useToggle(). Без аргументов useToggle работает как обычный булевый тумблер, но код получается короче, а имя функции сразу объясняет её смысл, в отличие от обезличенного setFlag.

Хук useCounter: счётчик с ограничениями

Для всего, что связано со счётом — количество товара в корзине, номер текущего шага визарда, рейтинг звёздами — в @mantine/hooks есть useCounter. В отличие от ручного useState<number>, он сразу умеет ограничивать значение диапазоном min и max и предоставляет готовый набор функций.

import { useCounter } from '@mantine/hooks';
 
function QuantityInput() {
  const [count, handlers] = useCounter(1, { min: 1, max: 10 });
 
  return (
    <div>
      <button onClick={handlers.decrement}>−</button>
      <span>{count}</span>
      <button onClick={handlers.increment}>+</button>
      <button onClick={handlers.reset}>Сбросить</button>
    </div>
  );
}

Обратите внимание: если вызвать handlers.increment, когда значение уже равно max, счётчик просто не изменится — хук сам следит за границами, и не нужно на каждый клик писать проверку if (count < max). Кроме increment, decrement и reset, есть ещё handlers.set(value) для установки конкретного числа с той же автоматической проверкой диапазона.

Робот собирает из блоков сайт

Собери свой код. Запусти сайт!

От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.

Собери свой первый проект под руководством практикующих разработчиков.

Подробнее о курсе

Комбинируем хуки в реальном компоненте

По-настоящему хуки раскрываются, когда работают вместе. Соберём типовой блок «добавить в корзину»: счётчик количества товара через useCounter и модальное окно подтверждения через useDisclosure, которое появляется, если пользователь пытается добавить больше десяти единиц.

import { Modal, Button, Group, Text } from '@mantine/core';
import { useCounter, useDisclosure } from '@mantine/hooks';
 
const LIMIT = 10;
 
function AddToCart() {
  const [count, handlers] = useCounter(1, { min: 1, max: LIMIT });
  const [opened, { open, close }] = useDisclosure(false);
 
  const handleAdd = () => {
    if (count >= LIMIT) {
      open();
      return;
    }
 
    console.log('Добавлено в корзину:', count);
  };
 
  return (
    <>
      <Modal opened={opened} onClose={close} title="Достигнут лимит">
        <Text>Нельзя добавить больше {LIMIT} единиц товара за раз.</Text>
        <Button onClick={close}>Понятно</Button>
      </Modal>
 
      <Group>
        <Button onClick={handlers.decrement}>−</Button>
        <Text>{count}</Text>
        <Button onClick={handlers.increment}>+</Button>
        <Button onClick={handleAdd}>Добавить в корзину</Button>
      </Group>
    </>
  );
}

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

Если одна и та же комбинация хуков — например, useDisclosure плюс useCounter с определённой логикой — повторяется в нескольких местах приложения, вынесите её в собственный кастомный хук вроде useQuantitySelector. Это уберёт дублирование, а компонент, который использует хук, останется чистым и будет отвечать только за разметку, а не за бизнес-логику.

Итоги

В этом уроке мы познакомились с тремя хуками из @mantine/hooks, которые закрывают самые частые задачи по управлению локальным состоянием.

  1. Разобрали useDisclosure для управления видимостью модалок, панелей и других элементов
  2. Связали useDisclosure с компонентом Modal на практике
  3. Изучили useToggle для переключения между двумя и более значениями
  4. Освоили useCounter с автоматическим ограничением диапазона через min и max
  5. Собрали все три хука в одном практическом компоненте с корзиной

В следующем уроке перейдём к работе с более сложным состоянием: разберём хуки useInputState, useListState и useSetState, которые пригодятся, когда одного булевого флага или числа уже недостаточно.

Теги: