npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@budarin/use-media-query

v1.0.3

Published

React hook for media queries

Readme

useMediaQuery

Современный React хук для медиа-запросов, построенный на useSyncExternalStore для оптимальной производительности и совместимости с SSR.

🇺🇸 English documentation

Возможности

  • SSR Безопасный - Нет рассинхронизации при гидратации
  • Оптимизированная производительность - Построен на useSyncExternalStore
  • Автоматические обновления - Автоматически реагирует на изменение размера окна, смену ориентации и все обновления viewport
  • Поддержка TypeScript - Полные определения типов
  • Современный React - Совместим с React 18+
  • Нулевые зависимости - Легковесная реализация

Установка

npm install @budarin/useMediaQuery
# или
pnpm add @budarin/useMediaQuery
# или
yarn add @budarin/useMediaQuery

Использование

Базовый пример

import { useMediaQuery } from '@budarin/useMediaQuery';

function App() {
    const isMobile = useMediaQuery('(max-width: 768px)');
    const isDark = useMediaQuery('(prefers-color-scheme: dark)');

    return (
        <div>
            <h1>{isMobile ? 'Мобильный вид' : 'Десктопный вид'}</h1>
            <p>Тема: {isDark ? 'Темная' : 'Светлая'}</p>
        </div>
    );
}

Продвинутые примеры

import { useMediaQuery } from '@budarin/useMediaQuery';

function ResponsiveComponent() {
    const isSmall = useMediaQuery('(max-width: 640px)');
    const isMedium = useMediaQuery(
        '(min-width: 641px) and (max-width: 1024px)'
    );
    const isLarge = useMediaQuery('(min-width: 1025px)');
    const isLandscape = useMediaQuery('(orientation: landscape)');
    const prefersReducedMotion = useMediaQuery(
        '(prefers-reduced-motion: reduce)'
    );

    return (
        <div>
            {isSmall && <MobileLayout />}
            {isMedium && <TabletLayout />}
            {isLarge && <DesktopLayout />}

            {isLandscape && <LandscapeWarning />}

            <div
                style={{
                    animation: prefersReducedMotion ? 'none' : 'fadeIn 0.3s',
                }}
            >
                Контент
            </div>
        </div>
    );
}

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

import { useMediaQuery } from '@budarin/useMediaQuery';

// Создаем кастомные хуки для стандартных брейкпоинтов
export const useBreakpoints = () => {
    const isMobile = useMediaQuery('(max-width: 767px)');
    const isTablet = useMediaQuery(
        '(min-width: 768px) and (max-width: 1023px)'
    );
    const isDesktop = useMediaQuery('(min-width: 1024px)');

    return { isMobile, isTablet, isDesktop };
};

// Использование
function MyComponent() {
    const { isMobile, isTablet, isDesktop } = useBreakpoints();

    return (
        <div>
            {isMobile && <MobileView />}
            {isTablet && <TabletView />}
            {isDesktop && <DesktopView />}
        </div>
    );
}

API

useMediaQuery(query: string): boolean

Параметры:

  • query (string) - CSS медиа-запрос

Возвращает:

  • boolean - Соответствует ли медиа-запрос

Почему следует использовать именно этот хук?

Этот хук построен на React useSyncExternalStore вместо традиционного паттерна useState + useEffect по нескольким причинам:

Проблемы:

  • Рассинхронизация при гидратации между сервером и клиентом
  • Состояния гонки между useState и useEffect
  • Устаревшее состояние, если медиа-запрос изменился до монтирования компонента
  • Плохая совместимость с SSR
  • Проблемы с concurrent рендерингом в React 18+ - Состояние может стать неконсистентным во время прерванных рендеров

✅ Преимущества useSyncExternalStore

  • Синхронная синхронизация - Всегда актуальное состояние
  • SSR безопасность - Нет рассинхронизации при гидратации
  • Нет состояний гонки - Store всегда синхронизирован
  • Лучшая производительность - React оптимизирует подписки
  • Готовность к будущему - Построен для React 18+ concurrent функций
  • Автоматическое отслеживание viewport - Событие MediaQueryList.change автоматически срабатывает при изменении размера окна, смене ориентации и любых обновлениях viewport
  • Безопасность concurrent рендеринга - Нет неконсистентности состояния во время прерванных рендеров

🚨 Критическая проблема React 18+

С concurrent функциями React 18+ традиционный подход useState + useEffect может вызвать неконсистентность состояния:

// ❌ Проблематично с concurrent рендерингом
function useMediaQuery(query) {
    const [matches, setMatches] = useState(
        () => window.matchMedia(query).matches
    );

    useEffect(() => {
        const mediaQuery = window.matchMedia(query);
        const handler = (e) => setMatches(e.matches); // ⚠️ Может быть вызван во время прерванного рендера
        mediaQuery.addEventListener('change', handler);
        return () => mediaQuery.removeEventListener('change', handler);
    }, [query]);

    return matches; // ⚠️ Может вернуть устаревшее состояние во время concurrent обновлений
}

Проблема:

  • React может прерывать рендеры в React 18+
  • Изменения медиа-запросов могут происходить во время прерванных рендеров
  • Обновления useState могут применяться не в том порядке
  • Компонент может отрендериться с неконсистентным состоянием

Решение:

  • useSyncExternalStore гарантирует, что состояние всегда синхронизировано с внешним источником
  • Нет состояний гонки во время concurrent рендеринга
  • Гарантированная консистентность даже с прерванными рендерами

TypeScript

Полная поддержка TypeScript включена. Дополнительные определения типов не нужны.

import { useMediaQuery } from '@budarin/useMediaQuery';

// TypeScript автоматически выводит тип возвращаемого значения
const isMobile: boolean = useMediaQuery('(max-width: 768px)');

Лицензия

MIT

Автор

Vadim Budarin