|
1 | | -import { useEffect, useState } from "react"; |
| 1 | +import { useCallback, useSyncExternalStore } from "react"; |
2 | 2 |
|
3 | | -function getDevice(): "mobile" | "tablet" | "desktop" | null { |
4 | | - if (typeof window === "undefined") return null; |
| 3 | +type MediaQuery = `(${string}:${string})`; |
5 | 4 |
|
6 | | - return window.matchMedia("(min-width: 1024px)").matches |
7 | | - ? "desktop" |
8 | | - : window.matchMedia("(min-width: 640px)").matches |
9 | | - ? "tablet" |
10 | | - : "mobile"; |
| 5 | +function getMediaQueryMatch(query: MediaQuery) { |
| 6 | + return window.matchMedia(query).matches; |
11 | 7 | } |
12 | 8 |
|
13 | | -function getDimensions() { |
14 | | - if (typeof window === "undefined") return null; |
15 | | - |
16 | | - return { width: window.innerWidth, height: window.innerHeight }; |
| 9 | +function addMediaQueryListener(query: MediaQuery, onChange: () => void) { |
| 10 | + const mediaQueryList = window.matchMedia(query); |
| 11 | + mediaQueryList.addEventListener("change", onChange); |
| 12 | + return function cleanup() { |
| 13 | + mediaQueryList.removeEventListener("change", onChange); |
| 14 | + }; |
17 | 15 | } |
18 | 16 |
|
19 | | -export function useMediaQuery() { |
20 | | - const [device, setDevice] = useState<"mobile" | "tablet" | "desktop" | null>( |
21 | | - getDevice(), |
| 17 | +function useMediaQuerySync(query: MediaQuery) { |
| 18 | + const subscribeToMediaQuery = useCallback( |
| 19 | + (onChange: () => void) => addMediaQueryListener(query, onChange), |
| 20 | + [query], |
22 | 21 | ); |
23 | | - const [dimensions, setDimensions] = useState<{ |
24 | | - width: number; |
25 | | - height: number; |
26 | | - } | null>(getDimensions()); |
27 | | - |
28 | | - useEffect(() => { |
29 | | - const checkDevice = () => { |
30 | | - setDevice(getDevice()); |
31 | | - setDimensions(getDimensions()); |
32 | | - }; |
33 | | - |
34 | | - // Initial detection |
35 | | - checkDevice(); |
36 | | - |
37 | | - // Listener for windows resize |
38 | | - window.addEventListener("resize", checkDevice); |
39 | | - |
40 | | - // Cleanup listener |
41 | | - return () => { |
42 | | - window.removeEventListener("resize", checkDevice); |
43 | | - }; |
44 | | - }, []); |
| 22 | + const matches = useSyncExternalStore( |
| 23 | + subscribeToMediaQuery, |
| 24 | + function getSnapshot() { |
| 25 | + return getMediaQueryMatch(query); |
| 26 | + }, |
| 27 | + function getServerSnapshot() { |
| 28 | + return false; |
| 29 | + }, |
| 30 | + ); |
| 31 | + return matches; |
| 32 | +} |
45 | 33 |
|
| 34 | +export function useMediaQuery() { |
46 | 35 | return { |
47 | | - device, |
48 | | - width: dimensions?.width, |
49 | | - height: dimensions?.height, |
50 | | - isMobile: device === "mobile", |
51 | | - isTablet: device === "tablet", |
52 | | - isDesktop: device === "desktop", |
| 36 | + isDesktop: useMediaQuerySync("(min-width: 1024px)"), |
| 37 | + isTablet: useMediaQuerySync("(min-width: 640px) and (max-width: 1023px)"), |
| 38 | + isMobile: useMediaQuerySync("(max-width: 639px)"), |
53 | 39 | }; |
54 | 40 | } |
0 commit comments