@@ -26,42 +26,24 @@ export type MuiMediaQueryListListener = (event: MuiMediaQueryListEvent) => void;
26
26
export interface Options {
27
27
defaultMatches ?: boolean ;
28
28
matchMedia ?: typeof window . matchMedia ;
29
+ /**
30
+ * This option is kept for backwards compatibility and has no longer any effect.
31
+ * It's previous behavior is now handled automatically.
32
+ */
33
+ // TODO: Deprecate for v6
29
34
noSsr ?: boolean ;
30
35
ssrMatchMedia ?: ( query : string ) => { matches : boolean } ;
31
36
}
32
37
33
- export default function useMediaQuery < Theme = unknown > (
34
- queryInput : string | ( ( theme : Theme ) => string ) ,
35
- options : Options = { } ,
38
+ function useMediaQueryOld (
39
+ query : string ,
40
+ defaultMatches : boolean ,
41
+ matchMedia : typeof window . matchMedia | null ,
42
+ ssrMatchMedia : ( ( query : string ) => { matches : boolean } ) | null ,
43
+ noSsr : boolean | undefined ,
36
44
) : boolean {
37
- const theme = useTheme < Theme > ( ) ;
38
- // Wait for jsdom to support the match media feature.
39
- // All the browsers MUI support have this built-in.
40
- // This defensive check is here for simplicity.
41
- // Most of the time, the match media logic isn't central to people tests.
42
45
const supportMatchMedia =
43
46
typeof window !== 'undefined' && typeof window . matchMedia !== 'undefined' ;
44
- const {
45
- defaultMatches = false ,
46
- matchMedia = supportMatchMedia ? window . matchMedia : null ,
47
- noSsr = false ,
48
- ssrMatchMedia = null ,
49
- } = getThemeProps ( { name : 'MuiUseMediaQuery' , props : options , theme } ) ;
50
-
51
- if ( process . env . NODE_ENV !== 'production' ) {
52
- if ( typeof queryInput === 'function' && theme === null ) {
53
- console . error (
54
- [
55
- 'MUI: The `query` argument provided is invalid.' ,
56
- 'You are providing a function without a theme in the context.' ,
57
- 'One of the parent elements needs to use a ThemeProvider.' ,
58
- ] . join ( '\n' ) ,
59
- ) ;
60
- }
61
- }
62
-
63
- let query = typeof queryInput === 'function' ? queryInput ( theme ) : queryInput ;
64
- query = query . replace ( / ^ @ m e d i a ( ? ) / m, '' ) ;
65
47
66
48
const [ match , setMatch ] = React . useState ( ( ) => {
67
49
if ( noSsr && supportMatchMedia ) {
@@ -93,13 +75,101 @@ export default function useMediaQuery<Theme = unknown>(
93
75
}
94
76
} ;
95
77
updateMatch ( ) ;
78
+ // TODO: Use `addEventListener` once support for Safari < 14 is dropped
96
79
queryList . addListener ( updateMatch ) ;
97
80
return ( ) => {
98
81
active = false ;
99
82
queryList . removeListener ( updateMatch ) ;
100
83
} ;
101
84
} , [ query , matchMedia , supportMatchMedia ] ) ;
102
85
86
+ return match ;
87
+ }
88
+
89
+ // eslint-disable-next-line no-useless-concat -- Workaround for https://github.com/webpack/webpack/issues/14814
90
+ const maybeReactUseSyncExternalStore : undefined | any = ( React as any ) [ 'useSyncExternalStore' + '' ] ;
91
+
92
+ function useMediaQueryNew (
93
+ query : string ,
94
+ defaultMatches : boolean ,
95
+ matchMedia : typeof window . matchMedia | null ,
96
+ ssrMatchMedia : ( ( query : string ) => { matches : boolean } ) | null ,
97
+ ) : boolean {
98
+ const getDefaultSnapshot = React . useCallback ( ( ) => defaultMatches , [ defaultMatches ] ) ;
99
+ const getServerSnapshot = React . useMemo ( ( ) => {
100
+ if ( ssrMatchMedia !== null ) {
101
+ const { matches } = ssrMatchMedia ( query ) ;
102
+ return ( ) => matches ;
103
+ }
104
+ return getDefaultSnapshot ;
105
+ } , [ getDefaultSnapshot , query , ssrMatchMedia ] ) ;
106
+ const [ getSnapshot , subscribe ] = React . useMemo ( ( ) => {
107
+ if ( matchMedia === null ) {
108
+ return [ getDefaultSnapshot , ( ) => ( ) => { } ] ;
109
+ }
110
+
111
+ const mediaQueryList = matchMedia ( query ) ;
112
+
113
+ return [
114
+ ( ) => mediaQueryList . matches ,
115
+ ( notify : ( ) => void ) => {
116
+ // TODO: Use `addEventListener` once support for Safari < 14 is dropped
117
+ mediaQueryList . addListener ( notify ) ;
118
+ return ( ) => {
119
+ mediaQueryList . removeListener ( notify ) ;
120
+ } ;
121
+ } ,
122
+ ] ;
123
+ } , [ getDefaultSnapshot , matchMedia , query ] ) ;
124
+ const match = maybeReactUseSyncExternalStore ( subscribe , getSnapshot , getServerSnapshot ) ;
125
+
126
+ return match ;
127
+ }
128
+
129
+ export default function useMediaQuery < Theme = unknown > (
130
+ queryInput : string | ( ( theme : Theme ) => string ) ,
131
+ options : Options = { } ,
132
+ ) : boolean {
133
+ const theme = useTheme < Theme > ( ) ;
134
+ // Wait for jsdom to support the match media feature.
135
+ // All the browsers MUI support have this built-in.
136
+ // This defensive check is here for simplicity.
137
+ // Most of the time, the match media logic isn't central to people tests.
138
+ const supportMatchMedia =
139
+ typeof window !== 'undefined' && typeof window . matchMedia !== 'undefined' ;
140
+ const {
141
+ defaultMatches = false ,
142
+ matchMedia = supportMatchMedia ? window . matchMedia : null ,
143
+ ssrMatchMedia = null ,
144
+ noSsr,
145
+ } = getThemeProps ( { name : 'MuiUseMediaQuery' , props : options , theme } ) ;
146
+
147
+ if ( process . env . NODE_ENV !== 'production' ) {
148
+ if ( typeof queryInput === 'function' && theme === null ) {
149
+ console . error (
150
+ [
151
+ 'MUI: The `query` argument provided is invalid.' ,
152
+ 'You are providing a function without a theme in the context.' ,
153
+ 'One of the parent elements needs to use a ThemeProvider.' ,
154
+ ] . join ( '\n' ) ,
155
+ ) ;
156
+ }
157
+ }
158
+
159
+ let query = typeof queryInput === 'function' ? queryInput ( theme ) : queryInput ;
160
+ query = query . replace ( / ^ @ m e d i a ( ? ) / m, '' ) ;
161
+
162
+ // TODO: Drop `useMediaQueryOld` and use `use-sync-external-store` shim in `useMediaQueryNew` once the package is stable
163
+ const useMediaQueryImplementation =
164
+ maybeReactUseSyncExternalStore !== undefined ? useMediaQueryNew : useMediaQueryOld ;
165
+ const match = useMediaQueryImplementation (
166
+ query ,
167
+ defaultMatches ,
168
+ matchMedia ,
169
+ ssrMatchMedia ,
170
+ noSsr ,
171
+ ) ;
172
+
103
173
if ( process . env . NODE_ENV !== 'production' ) {
104
174
// eslint-disable-next-line react-hooks/rules-of-hooks
105
175
React . useDebugValue ( { query, match } ) ;
0 commit comments