All files / hooks/use-media-query/src useMediaQuery.ts

100% Statements 20/20
100% Branches 9/9
100% Functions 6/6
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71                                                                      14x   14x 8x 7x   1x     14x 9x 1x     8x   8x 11x       8x   8x 7x 7x       1x 1x     14x    
import { useEffect, useState } from "react";
import type { UseMediaQueryOptions } from "./types";
import { getMatches, isMatchMediaSupported } from "./utils";
 
/**
 * Tracks whether a CSS media query currently matches, updating on change.
 *
 * Built on `window.matchMedia` with a modern `change` listener (and a legacy
 * `addListener` fallback). SSR-safe: returns `defaultValue` on the server and,
 * optionally, on the first client render to avoid hydration mismatches.
 *
 * @param query - A CSS media query, e.g. `"(min-width: 1024px)"` or
 * `"(prefers-color-scheme: dark)"`
 * @param options - Configuration (SSR default value, eager initialization)
 * @returns `true` when the query matches, else `false`
 *
 * @example
 * ```tsx
 * const isDesktop = useMediaQuery("(min-width: 1024px)");
 * return isDesktop ? <DesktopNav /> : <MobileNav />;
 * ```
 *
 * @example
 * ```tsx
 * // SSR: keep the first client render matching the server output
 * const isWide = useMediaQuery("(min-width: 1024px)", {
 *   defaultValue: false,
 *   initializeWithValue: false,
 * });
 * ```
 */
export function useMediaQuery(
  query: string,
  options: UseMediaQueryOptions = {}
): boolean {
  const { defaultValue = false, initializeWithValue = true } = options;
 
  const [matches, setMatches] = useState<boolean>(() => {
    if (initializeWithValue) {
      return getMatches(query, defaultValue);
    }
    return defaultValue;
  });
 
  useEffect(() => {
    if (!isMatchMediaSupported()) {
      return;
    }
 
    const mediaQueryList = window.matchMedia(query);
 
    const handleChange = () => {
      setMatches(mediaQueryList.matches);
    };
 
    // Sync immediately in case the query changed since the last render.
    handleChange();
 
    if (typeof mediaQueryList.addEventListener === "function") {
      mediaQueryList.addEventListener("change", handleChange);
      return () => mediaQueryList.removeEventListener("change", handleChange);
    }
 
    // Legacy Safari (< 14) fallback.
    mediaQueryList.addListener(handleChange);
    return () => mediaQueryList.removeListener(handleChange);
  }, [query]);
 
  return matches;
}