React cookbook · Beginner

Respond to media queries in React

Render different components for mobile and desktop and react to changes like dark mode or reduced motion.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Subscribe to window.matchMedia with useSyncExternalStore, which avoids tearing and handles subscriptions correctly. Provide a server snapshot for SSR. Prefer CSS media queries for pure styling; use the hook when behavior or rendered components differ.

Solution

import { useCallback, useSyncExternalStore } from "react";

export function useMediaQuery(query: string, serverDefault = false): boolean {
  const subscribe = useCallback(
    (onChange: () => void) => {
      const mql = window.matchMedia(query);
      mql.addEventListener("change", onChange);
      return () => mql.removeEventListener("change", onChange);
    },
    [query],
  );
  return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(query).matches,
    () => serverDefault,
  );
}

export function Navigation() {
  const isDesktop = useMediaQuery("(min-width: 1024px)");
  const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
  return isDesktop ? <Sidebar animate={!reduceMotion} /> : <BottomTabs />;
}

Pitfalls

  • Reading window during server rendering.
  • Using JS media queries for layout that CSS can handle.
  • Ignoring prefers-reduced-motion for animations.

Variations

  • Container queries in CSS for component-level responsiveness.

Further reading: react.dev: useSyncExternalStore

Reference: https://react.dev/reference/react/useSyncExternalStore

More React recipes

Was this page helpful?