Skip to content

useMedia, useResponsiveValue: share MediaQueryList instances and listeners #8720

Open

Description

@mattcosta7

Part of #8676 (P1).

Problem

  • hooks/useResponsiveValue.ts:54-56 always calls useMedia three times, once each for narrow, regular and wide, even when the value isn't responsive.
  • hooks/useMedia.ts:54 calls window.matchMedia(...) inside getSnapshot, so every render allocates a new MediaQueryList.
  • Each hook instance adds its own change listener.

Measured: one closed ActionMenu holds 9 live change listeners across 3 distinct queries.

Proposed fix

  • Add a module-level Map<query, MediaQueryList> with reference-counted listeners, shared by every useMedia caller.
  • Have getSnapshot read the cached .matches.
  • Optionally skip the useMedia calls in useResponsiveValue when the value isn't responsive, without breaking the rules of hooks.

Keep the hydration behavior from #8001. The fix must work on React 18 and 19.

Acceptance criteria

  • Each distinct query has one MediaQueryList and one change listener, however many components subscribe.
  • The listener is removed when the last subscriber unmounts.
  • SSR and hydration behavior doesn't change, and the existing useMedia tests pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions