React to viewport and media changes.
useMediaQuery
jsxfunction useMediaQuery(query) { const [matches, setMatches] = useState(() => { if (typeof window === 'undefined') return false; return window.matchMedia(query).matches; }); useEffect(() => { const mediaQuery = window.matchMedia(query); setMatches(mediaQuery.matches); const handler = (event) => setMatches(event.matches); // Modern browsers mediaQuery.addEventListener('change', handler); return () => mediaQuery.removeEventListener('change', handler); }, [query]); return matches; } // Usage function ResponsiveLayout() { const isMobile = useMediaQuery('(max-width: 768px)'); const prefersDark = useMediaQuery('(prefers-color-scheme: dark)'); const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)'); return ( <div className={prefersDark ? 'dark' : 'light'}> {isMobile ? <MobileNav /> : <DesktopNav />} </div> ); }
Breakpoint Hooks
jsxconst breakpoints = { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', }; function useBreakpoint() { const isSm = useMediaQuery(`(min-width: ${breakpoints.sm})`); const isMd = useMediaQuery(`(min-width: ${breakpoints.md})`); const isLg = useMediaQuery(`(min-width: ${breakpoints.lg})`); const isXl = useMediaQuery(`(min-width: ${breakpoints.xl})`); return useMemo(() => ({ isSm, isMd, isLg, isXl, current: isXl ? 'xl' : isLg ? 'lg' : isMd ? 'md' : isSm ? 'sm' : 'xs', }), [isSm, isMd, isLg, isXl]); } // Usage function Gallery() { const { current, isMd } = useBreakpoint(); const columns = { xs: 1, sm: 2, md: 3, lg: 4, xl: 5, }[current]; return <Grid columns={columns}>...</Grid>; }
useWindowSize
jsxfunction useWindowSize() { const [size, setSize] = useState({ width: typeof window !== 'undefined' ? window.innerWidth : 0, height: typeof window !== 'undefined' ? window.innerHeight : 0, }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return size; } // With debounce for performance function useWindowSize({ debounceMs = 100 } = {}) { const [size, setSize] = useState({ width: typeof window !== 'undefined' ? window.innerWidth : 0, height: typeof window !== 'undefined' ? window.innerHeight : 0, }); useEffect(() => { let timeoutId; const handleResize = () => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { setSize({ width: window.innerWidth, height: window.innerHeight, }); }, debounceMs); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); clearTimeout(timeoutId); }; }, [debounceMs]); return size; }
useOrientation
jsxfunction useOrientation() { const [orientation, setOrientation] = useState(() => { if (typeof window === 'undefined') return 'portrait'; return window.innerWidth > window.innerHeight ? 'landscape' : 'portrait'; }); useEffect(() => { const handleOrientationChange = () => { setOrientation( window.innerWidth > window.innerHeight ? 'landscape' : 'portrait' ); }; window.addEventListener('resize', handleOrientationChange); window.addEventListener('orientationchange', handleOrientationChange); return () => { window.removeEventListener('resize', handleOrientationChange); window.removeEventListener('orientationchange', handleOrientationChange); }; }, []); return orientation; }