22'use client' ;
33
44import { noSSRError } from '../no-ssr' ;
5- import { noop } from '../noop' ;
65
7- import { useCallback , useSyncExternalStore } from 'react' ;
8- import { createEventTargetBus } from 'event-target-bus' ;
9- import type { EventTargetBus } from 'event-target-bus' ;
6+ import { useMemo , useSyncExternalStore } from 'react' ;
7+ import { createKeyedSyncExternalStoreSubscribe } from 'event-target-bus/react' ;
108
11- const mediaQueryProxies = new Map < string , EventTargetBus < MediaQueryList , 'change' > > ( ) ;
12-
13- function subscribeToMediaQuery ( mq : string , callback : VoidFunction ) {
14- /* istanbul ignore if -- SSR-only guard, unreachable when Happy DOM registers window globally in tests */
15- if ( typeof window === 'undefined' ) return noop ;
16-
17- let bus = mediaQueryProxies . get ( mq ) ;
18- if ( ! bus ) {
19- bus = createEventTargetBus ( window . matchMedia ( mq ) , 'change' ) ;
20- mediaQueryProxies . set ( mq , bus ) ;
21- }
22-
23- return bus . on ( callback ) ;
24- }
9+ const getMediaQuerySubscribe = createKeyedSyncExternalStoreSubscribe (
10+ ( mq : string ) => window . matchMedia ( mq ) ,
11+ 'change'
12+ ) ;
2513
2614function getServerSnapshotWithoutServerValue ( ) : never {
2715 throw noSSRError ( 'useMediaQuery cannot be used on the server without a serverValue' ) ;
@@ -30,9 +18,6 @@ function getServerSnapshotWithoutServerValue(): never {
3018/** @see https://foxact.skk.moe/use-media-query */
3119// eslint-disable-next-line sukka/bool-param-default -- serveValue is intentionally optional
3220export function useMediaQuery ( mq : string , serverValue ?: boolean ) : boolean {
33- // subscribe once per hook per media query
34- const subscribe = useCallback ( ( callback : VoidFunction ) => subscribeToMediaQuery ( mq , callback ) , [ mq ] ) ;
35-
3621 const getSnapshot = ( ) => {
3722 /* istanbul ignore if -- SSR-only guard, unreachable when Happy DOM registers window globally in tests */
3823 if ( typeof window === 'undefined' ) {
@@ -48,5 +33,8 @@ export function useMediaQuery(mq: string, serverValue?: boolean): boolean {
4833 ? getServerSnapshotWithoutServerValue
4934 : ( ) => serverValue ;
5035
36+ // ensure stableness per mq
37+ const subscribe = useMemo ( ( ) => getMediaQuerySubscribe ( mq ) , [ mq ] ) ;
38+
5139 return useSyncExternalStore ( subscribe , getSnapshot , getServerSnapshot ) ; // Use useSyncExternalStore to manage the subscription and state
5240}
0 commit comments