← Back to snippets
typescriptreacthooksresponsivemedia-query

useMediaQuery Hook

React hook for responsive design with media queries. SSR-safe with proper hydration handling and TypeScript support.

Code

import { useState, useEffect, useCallback } from 'react';

function useMediaQuery(query: string): boolean {
  // Return false during SSR to avoid hydration mismatch
  const getMatches = useCallback((): boolean => {
    if (typeof window === 'undefined') return false;
    return window.matchMedia(query).matches;
  }, [query]);

  const [matches, setMatches] = useState<boolean>(getMatches);

  useEffect(() => {
    if (typeof window === 'undefined') return;

    const mediaQuery = window.matchMedia(query);

    // Set initial value after mount (handles SSR hydration)
    setMatches(mediaQuery.matches);

    const handler = (event: MediaQueryListEvent) => {
      setMatches(event.matches);
    };

    // Modern browsers
    mediaQuery.addEventListener('change', handler);

    return () => {
      mediaQuery.removeEventListener('change', handler);
    };
  }, [query]);

  return matches;
}

// Usage
const isMobile = useMediaQuery('(max-width: 768px)');
const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
const isDesktop = useMediaQuery('(min-width: 1025px)');
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');

Variations

Prefers color scheme hook

function usePrefersColorScheme(): 'light' | 'dark' | 'no-preference' {
  const isDark = useMediaQuery('(prefers-color-scheme: dark)');
  const isLight = useMediaQuery('(prefers-color-scheme: light)');

  if (isDark) return 'dark';
  if (isLight) return 'light';
  return 'no-preference';
}

// Usage
function ThemeProvider({ children }: { children: React.ReactNode }) {
  const colorScheme = usePrefersColorScheme();
  const [theme, setTheme] = useState(colorScheme === 'dark' ? 'dark' : 'light');

  // Sync with system preference unless user has explicitly chosen
  const [userOverride, setUserOverride] = useState(false);

  useEffect(() => {
    if (!userOverride) {
      setTheme(colorScheme === 'dark' ? 'dark' : 'light');
    }
  }, [colorScheme, userOverride]);

  return <div data-theme={theme}>{children}</div>;
}