@@ -26,42 +26,24 @@ export type MuiMediaQueryListListener = (event: MuiMediaQueryListEvent) => void;
2626export interface Options {
2727 defaultMatches ?: boolean ;
2828 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
2934 noSsr ?: boolean ;
3035 ssrMatchMedia ?: ( query : string ) => { matches : boolean } ;
3136}
3237
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 ,
3644) : 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.
4245 const supportMatchMedia =
4346 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, '' ) ;
6547
6648 const [ match , setMatch ] = React . useState ( ( ) => {
6749 if ( noSsr && supportMatchMedia ) {
@@ -93,13 +75,101 @@ export default function useMediaQuery<Theme = unknown>(
9375 }
9476 } ;
9577 updateMatch ( ) ;
78+ // TODO: Use `addEventListener` once support for Safari < 14 is dropped
9679 queryList . addListener ( updateMatch ) ;
9780 return ( ) => {
9881 active = false ;
9982 queryList . removeListener ( updateMatch ) ;
10083 } ;
10184 } , [ query , matchMedia , supportMatchMedia ] ) ;
10285
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+
103173 if ( process . env . NODE_ENV !== 'production' ) {
104174 // eslint-disable-next-line react-hooks/rules-of-hooks
105175 React . useDebugValue ( { query, match } ) ;
0 commit comments