From 7d725c418da2f002649fbfb5afe340ee35359b18 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Tue, 19 May 2026 10:19:26 +0200 Subject: [PATCH 1/3] [useMediaQuery] switch to uSES There were reports of crash in React 19 due to a lot of re-renders on mount when there are a lot of components, because of the setState in React.useLayoutEffect --- .../react-responsive/src/useMediaQuery.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/react-responsive/src/useMediaQuery.ts b/packages/react-responsive/src/useMediaQuery.ts index b468191..8ca3815 100644 --- a/packages/react-responsive/src/useMediaQuery.ts +++ b/packages/react-responsive/src/useMediaQuery.ts @@ -1,22 +1,18 @@ import * as React from "react"; -export const useMediaQuery = (mediaQuery: string): boolean => { +export function useMediaQuery(mediaQuery: string): boolean { const mediaQueryList = React.useMemo(() => matchMedia(mediaQuery), [mediaQuery]); - const [isShown, setIsShown] = React.useState(mediaQueryList.matches); - React.useLayoutEffect(() => { - setIsShown(mediaQueryList.matches); - const listener = (event: MediaQueryListEvent) => { - // Those are important updates, so we don't want to use transitions on them - setIsShown(event.matches); - }; - - // cannot use addEventListener for IE 11 and safari 13- - mediaQueryList.addListener(listener); - return () => { - mediaQueryList.removeListener(listener); - }; - }, [mediaQueryList]); - - return isShown; -}; + // Those are important updates, so we don't want to use transitions on them + return React.useSyncExternalStore( + React.useCallback( + (callback) => { + // cannot use addEventListener for IE 11 and safari 13- + mediaQueryList.addListener(callback); + return () => mediaQueryList.removeListener(callback); + }, + [mediaQueryList], + ), + () => mediaQueryList.matches, + ); +} From 46852ee44d007fd59058313acf1e2040eb378102 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Tue, 19 May 2026 10:20:29 +0200 Subject: [PATCH 2/3] Switch to function instead of => for better debugging --- packages/react-responsive/src/BreakpointsContext.tsx | 8 +++----- packages/react-responsive/src/fromBreakpointToMedia.ts | 4 ++-- packages/react-responsive/src/mediaQueryBuilder.ts | 6 +++--- packages/react-responsive/src/sanitize.ts | 4 ++-- packages/react-responsive/src/useBreakpoint.ts | 4 ++-- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/react-responsive/src/BreakpointsContext.tsx b/packages/react-responsive/src/BreakpointsContext.tsx index 5ba8e1d..c2d4635 100644 --- a/packages/react-responsive/src/BreakpointsContext.tsx +++ b/packages/react-responsive/src/BreakpointsContext.tsx @@ -19,11 +19,11 @@ interface BreakpointsProviderProps { additionalBreakpoints?: ExposedBreakpoints; } -export const BreakpointsProvider: React.FunctionComponent> = ({ +export function BreakpointsProvider({ breakpoints = defaultBreakpoints, additionalBreakpoints, children, -}) => { +}: React.PropsWithChildren): React.ReactElement { return ( ); -}; - -BreakpointsProvider.displayName = "BreakpointsProvider"; +} diff --git a/packages/react-responsive/src/fromBreakpointToMedia.ts b/packages/react-responsive/src/fromBreakpointToMedia.ts index 79949fd..19e8b89 100644 --- a/packages/react-responsive/src/fromBreakpointToMedia.ts +++ b/packages/react-responsive/src/fromBreakpointToMedia.ts @@ -1,6 +1,6 @@ import { Breakpoint } from "./sanitize"; -export const fromBreakpointToMedia = (breakpoint: Breakpoint): string => { +export function fromBreakpointToMedia(breakpoint: Breakpoint): string { const mediaList: string[] = []; const [minValue, maxValue, unit, direction] = breakpoint; let str; @@ -18,4 +18,4 @@ export const fromBreakpointToMedia = (breakpoint: Breakpoint): string => { } return " " + mediaList.join(" and "); -}; +} diff --git a/packages/react-responsive/src/mediaQueryBuilder.ts b/packages/react-responsive/src/mediaQueryBuilder.ts index 362ef34..7f44fb7 100644 --- a/packages/react-responsive/src/mediaQueryBuilder.ts +++ b/packages/react-responsive/src/mediaQueryBuilder.ts @@ -1,9 +1,8 @@ import { Breakpoints } from "./sanitize"; import { fromBreakpointToMedia } from "./fromBreakpointToMedia"; -export const mediaQueryBuilder = - (breakpoints: Breakpoints) => - (on = ""): string => { +export function mediaQueryBuilder(breakpoints: Breakpoints) { + return function toMediaQuery(on = ""): string { if (!on) { return ""; } @@ -23,3 +22,4 @@ export const mediaQueryBuilder = } return mediaQuery; }; +} diff --git a/packages/react-responsive/src/sanitize.ts b/packages/react-responsive/src/sanitize.ts index 1cd3b11..a6d8b99 100644 --- a/packages/react-responsive/src/sanitize.ts +++ b/packages/react-responsive/src/sanitize.ts @@ -52,7 +52,7 @@ export interface Breakpoints { [breakpoint: string]: Breakpoint; } -export const sanitize = (inBreakpoints: ExposedBreakpoints): Breakpoints => { +export function sanitize(inBreakpoints: ExposedBreakpoints): Breakpoints { return Object.keys(inBreakpoints).reduce((breakpoints, breakpointName) => { const breakpoint = inBreakpoints[breakpointName]; @@ -91,4 +91,4 @@ export const sanitize = (inBreakpoints: ExposedBreakpoints): Breakpoints => { return breakpoints; }, {}); -}; +} diff --git a/packages/react-responsive/src/useBreakpoint.ts b/packages/react-responsive/src/useBreakpoint.ts index 06c8db0..5e001ed 100644 --- a/packages/react-responsive/src/useBreakpoint.ts +++ b/packages/react-responsive/src/useBreakpoint.ts @@ -6,11 +6,11 @@ import { mediaQueryBuilder } from "./mediaQueryBuilder"; import { useMediaQuery } from "./useMediaQuery"; -export const useBreakpoint = (on?: string): boolean => { +export function useBreakpoint(on?: string): boolean { const breakpoints = React.useContext(BreakpointsContext); const toMediaQuery = React.useMemo(() => mediaQueryBuilder(breakpoints), [breakpoints]); const mediaQuery = React.useMemo(() => toMediaQuery(on), [toMediaQuery, on]); return useMediaQuery(mediaQuery || "-"); -}; +} From bc5774c35ef044b5502d1e86d9addeea02651d45 Mon Sep 17 00:00:00 2001 From: Ayc0 Date: Tue, 19 May 2026 10:22:41 +0200 Subject: [PATCH 3/3] Add comment for SSR --- packages/react-responsive/src/useMediaQuery.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-responsive/src/useMediaQuery.ts b/packages/react-responsive/src/useMediaQuery.ts index 8ca3815..f9cf854 100644 --- a/packages/react-responsive/src/useMediaQuery.ts +++ b/packages/react-responsive/src/useMediaQuery.ts @@ -14,5 +14,6 @@ export function useMediaQuery(mediaQuery: string): boolean { [mediaQueryList], ), () => mediaQueryList.matches, + // Don't add `() => false`, so that node implementations can define their own behavior for server-side rendering via `mock-match-media` ); }