Хуки useDisclosure, useToggle, useCounter
Не каждое состояние интерфейса заслуживает написания собственного хука с нуля — булевый флаг «открыто/закрыто», переключатель темы или счётчик товаров в корзине встречаются в проектах десятками раз. Библиотека @mantine/hooks уже решила эти типовые задачи за вас. В этом уроке разберём три небольших, но незаменимых хука: 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, которые закрывают самые частые задачи по управлению локальным состоянием.
- Разобрали
useDisclosureдля управления видимостью модалок, панелей и других элементов - Связали
useDisclosureс компонентомModalна практике - Изучили
useToggleдля переключения между двумя и более значениями - Освоили
useCounterс автоматическим ограничением диапазона черезminиmax - Собрали все три хука в одном практическом компоненте с корзиной
В следующем уроке перейдём к работе с более сложным состоянием: разберём хуки useInputState, useListState и useSetState, которые пригодятся, когда одного булевого флага или числа уже недостаточно.