-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathuseMediaQuery.ts
89 lines (76 loc) · 2.19 KB
/
useMediaQuery.ts
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import useEffect from './useIsomorphicEffect'
import { useState } from 'react'
interface RefCountedMediaQueryList extends MediaQueryList {
refCount: number
}
const matchersByWindow = new WeakMap<
Window,
Map<string, RefCountedMediaQueryList>
>()
const getMatcher = (
query: string | null,
targetWindow?: Window,
): RefCountedMediaQueryList | undefined => {
if (!query || !targetWindow) return undefined
const matchers =
matchersByWindow.get(targetWindow) ||
new Map<string, RefCountedMediaQueryList>()
matchersByWindow.set(targetWindow, matchers)
let mql = matchers.get(query)
if (!mql) {
mql = targetWindow.matchMedia(query) as RefCountedMediaQueryList
mql.refCount = 0
matchers.set(mql.media, mql)
}
return mql
}
/**
* Match a media query and get updates as the match changes. The media string is
* passed directly to `window.matchMedia` and run as a Layout Effect, so initial
* matches are returned before the browser has a chance to paint.
*
* ```tsx
* function Page() {
* const isWide = useMediaQuery('min-width: 1000px')
*
* return isWide ? "very wide" : 'not so wide'
* }
* ```
*
* Media query lists are also reused globally, hook calls for the same query
* will only create a matcher once under the hood.
*
* @param query A media query
* @param targetWindow The window to match against, uses the globally available one as a default.
*/
export default function useMediaQuery(
query: string | null,
targetWindow: Window | undefined = typeof window === 'undefined'
? undefined
: window,
) {
const mql = getMatcher(query, targetWindow)
const [matches, setMatches] = useState(() => (mql ? mql.matches : false))
useEffect(() => {
let mql = getMatcher(query, targetWindow)
if (!mql) {
return setMatches(false)
}
let matchers = matchersByWindow.get(targetWindow!)
const handleChange = () => {
setMatches(mql!.matches)
}
mql.refCount++
mql.addListener(handleChange)
handleChange()
return () => {
mql!.removeListener(handleChange)
mql!.refCount--
if (mql!.refCount <= 0) {
matchers?.delete(mql!.media)
}
mql = undefined
}
}, [query])
return matches
}