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

Продвинутые хуки Mantine: гайд и примеры

Библиотека @mantine/hooks устроена так, что на каждую типовую задачу фронтенда находится готовый хук. Но есть тройка, без которой сложно представить хоть сколько-нибудь взрослый проект: useDebouncedValue, useMediaQuery и useClipboard. Они решают отложенную реакцию на ввод, адаптивную логику на уровне JavaScript и копирование текста в буфер обмена — причём без единой сторонней зависимости. Разберём все три хука по отдельности, а затем соберём из них рабочий компонент.

Продвинутые хуки Mantine: гайд и примеры

Зачем нужны продвинутые хуки Mantine

В прошлых уроках мы уже разбирали хуки для управления состоянием: useInputState>, useListState и useSetState помогают хранить данные, а useDisclosure и useToggle — переключать булевы флаги. Но иногда проблема не в самом состоянии, а в том, как часто и при каких условиях оно должно обновляться.

Представьте три типичные ситуации. Пользователь печатает в поле поиска — и на каждое нажатие клавиши улетает запрос к серверу. Верстка должна вести себя по-разному на мобильном и десктопе — но не только визуально, а и логически: например, показывать Drawer вместо Modal. Наконец, нужна кнопка «Скопировать», которая ещё и покажет пользователю, что копирование прошло успешно. Для всех трёх случаев в Mantine есть готовое решение.

useDebouncedValue: откладываем реакцию на изменения

Хук useDebouncedValue принимает значение и возвращает его же, но с задержкой. Пока значение продолжает часто меняться, наружу ничего не «пробивается» — обновление происходит только тогда, когда пользователь на мгновение остановился.

import { useState } from "react";
import { useDebouncedValue } from "@mantine/hooks";
import { TextInput } from "@mantine/core";
 
function SearchBox() {
  const [search, setSearch] = useState("");
  const [debounced] = useDebouncedValue(search, 400);
 
  return (
    <TextInput
      label="Поиск"
      value={search}
      onChange={(event) => setSearch(event.currentTarget.value)}
      description={`Ищем: ${debounced}`}
    />
  );
}

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

Настройка задержки и опция leading

Второй аргумент — время задержки в миллисекундах. Чем оно больше, тем реже срабатывает обновление, но тем заметнее пользователю «лаг» перед реакцией интерфейса. Для поиска обычно достаточно 300–500 мс, для тяжёлых вычислений можно ставить 800–1000 мс.

Третий, необязательный аргумент — объект с полем leading. Если передать { leading: true }, хук обновит значение сразу при первом изменении, а уже последующие быстрые изменения будет откладывать. Это полезно, когда важно мгновенно среагировать на первое действие пользователя, но не заваливать систему повторными запросами.

Типичные сценарии применения useDebouncedValue:

  1. Поиск и фильтрация по мере ввода текста
  2. Автосохранение черновика формы
  3. Валидация поля с обращением к серверу (проверка занятости email)
  4. Пересчёт тяжёлых вычислений при изменении слайдера

useMediaQuery: адаптивная логика в JavaScript

CSS-медиазапросы прекрасно справляются с адаптацией стилей, но не помогают, когда нужно изменить саму структуру компонентов — например, отрендерить Drawer на мобильном и Modal на десктопе. Для таких случаев в Mantine есть useMediaQuery — обёртка над браузерным matchMedia.

import { useMediaQuery } from "@mantine/hooks";
import { Drawer, Modal } from "@mantine/core";
 
function ResponsiveOverlay({ opened, onClose, children }: Props) {
  const isMobile = useMediaQuery("(max-width: 48em)", false, {
    getInitialValueInEffect: true,
  });
 
  if (isMobile) {
    return (
      <Drawer opened={opened} onClose={onClose} position="bottom">
        {children}
      &lt;/Drawer>
    );
  }
 
  return (
    <Modal opened={opened} onClose={onClose}>
      {children}
    &lt;/Modal>
  );
}

Первый аргумент — строка медиазапроса, второй — начальное значение до первого вычисления, третий — объект настроек. Обратите внимание на пару Drawer/Modal: это не смена стилей, а смена самого компонента, и здесь без хука не обойтись.

Лайфхак: при серверном рендеринге window.matchMedia недоступен, поэтому первый рендер на клиенте может отличаться от серверного и вызвать предупреждение о гидратации. Задавайте второй аргумент initialValue самым частым значением для вашей аудитории (например, false, если большинство пользователей заходит с десктопа), а опцию getInitialValueInEffect: true используйте, чтобы точное значение вычислялось уже после монтирования, а не блокировало первый рендер.

Хук реагирует на изменение размеров окна в реальном времени, поэтому при ресайзе браузера компонент сам переключится между Drawer и Modal без перезагрузки страницы.

useClipboard: копирование в один клик

Третий хук решает частую задачу — кнопку «Скопировать», которая ещё и информирует пользователя об успехе. Под капотом используется браузерный Clipboard API, доступный только в защищённом контексте (https или localhost).

import { useClipboard } from "@mantine/hooks";
import { Button, TextInput, Group } from "@mantine/core";
 
function ShareLink({ link }: { link: string }) {
  const clipboard = useClipboard({ timeout: 1000 });
 
  return (
    <Group>
      <TextInput value={link} readOnly style={{ flex: 1 }} />
      <Button color={clipboard.copied ? "teal" : "blue"} onClick={() => clipboard.copy(link)}>
        {clipboard.copied ? "Скопировано" : "Скопировать"}
      &lt;/Button>
    &lt;/Group>
  );
}

Хук возвращает объект с четырьмя полями: copy — функция копирования, copied — булев флаг «успешно скопировано», error — объект ошибки, если браузер отказал в доступе к буферу, и reset — функция для ручного сброса состояния. Значение copied автоматически возвращается в false через время, указанное в timeout, поэтому отдельный таймер писать не нужно.

Если вы поддерживаете старые браузеры без Clipboard API, всегда проверяйте поле error и показывайте пользователю запасной вариант — например, выделение текста вручную.

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

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

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

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

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

Собираем всё вместе: практический пример

Три хука отлично сочетаются в одном сценарии. Представим панель фильтрации товаров: поле поиска с debounce, адаптивный выбор между боковой панелью и нижней шторкой, а также кнопка «Поделиться ссылкой с текущими фильтрами».

import { useState } from "react";
import {
  useDebouncedValue,
  useMediaQuery,
  useClipboard,
} from "@mantine/hooks";
import { TextInput, Button, Drawer, Modal, Stack } from "@mantine/core";
 
function ProductFilters() {
  const [query, setQuery] = useState("");
  const [debouncedQuery] = useDebouncedValue(query, 350);
  const isMobile = useMediaQuery("(max-width: 48em)", false);
  const clipboard = useClipboard({ timeout: 800 });
 
  const shareUrl = `https://shop.example/catalog?q=${debouncedQuery}`;
  const Overlay = isMobile ? Drawer : Modal;
 
  return (
    <Overlay opened onClose={() => {}} title="Фильтры">
      <Stack>
        <TextInput
          label="Поиск по каталогу"
          value={query}
          onChange={(event) => setQuery(event.currentTarget.value)}
        />
        <Button onClick={() => clipboard.copy(shareUrl)}>
          {clipboard.copied ? "Ссылка скопирована" : "Поделиться фильтрами"}
        &lt;/Button>
      &lt;/Stack>
    &lt;/Overlay>
  );
}

Лайфхак: когда в одном компоненте собирается три и более хука с самостоятельной логикой, стоит вынести их в отдельный кастомный хук — например, useProductFilters(), который вернёт готовые query, debouncedQuery, isMobile и copyShareLink. Компонент останется маленьким и будет отвечать только за разметку, а логику фильтрации станет легко переиспользовать и тестировать отдельно от JSX.

Для наглядности сравним три хука между собой:

Хук Когда использовать
useDebouncedValue Отложить реакцию на часто меняющееся значение
useMediaQuery Изменить структуру компонентов в зависимости от размера экрана
useClipboard Скопировать текст и показать пользователю статус операции

Итоги

Мы разобрали три хука, которые закрывают частые практические задачи: useDebouncedValue сглаживает частые изменения, useMediaQuery переносит адаптивность из CSS в логику компонентов, а useClipboard избавляет от ручной работы с буфером обмена. Вместе они позволяют собрать удобную и отзывчивую панель фильтров буквально за несколько строк кода.

В следующем уроке познакомимся с расширениями Mantine — пакетами @mantine/dates, @mantine/charts и @mantine/dropzone, которые добавляют календари, графики и загрузку файлов поверх базовой библиотеки.

Теги: