Skip to content

Compose: Fix SSR crash in useMediaQuery and useViewportMatch#78725

Merged
mcsf merged 3 commits into
WordPress:trunkfrom
xavier-lc:fix/compose-ssr-window-reference-error
May 27, 2026
Merged

Compose: Fix SSR crash in useMediaQuery and useViewportMatch#78725
mcsf merged 3 commits into
WordPress:trunkfrom
xavier-lc:fix/compose-ssr-window-reference-error

Conversation

@xavier-lc

Copy link
Copy Markdown
Contributor

What?

Follow up to #76446.

Make useMediaQuery and useViewportMatch safe to call during server-side rendering. Today they throw ReferenceError: window is not defined the moment they're invoked under Node SSR or a jest-environment node test, unmounting the whole React tree.

Why?

#76446 added an optional view: Window argument to both hooks, defaulting to the global window:

const useViewportMatch = ( breakpoint, operator = '>=', view = window ) => {  }

export default function useMediaQuery(
    query?: string,
    view: Window = window
): boolean {  }

Default parameter expressions are evaluated unconditionally at every call. In a non-DOM environment, window is an unbound identifier, so the bare reference throws a ReferenceError before the function body ever runs — and the existing SSR-safe machinery inside the body (useSyncExternalStore(subscribe, getValue, () => false), with
() => false as the server snapshot) never gets a chance to execute.

How?

  • Resolve the view default lazily inside the parameter list with typeof window !== 'undefined' ? window : undefined. The default expression itself is now safe, and consumers passing an explicit view are unaffected.
  • Widen getMQLSubscriber's signature to accept Window | undefined and short-circuit to EMPTY_SUBSCRIBER when view is falsy, so the type matches the new reality and WeakMap.get(undefined) is impossible.

With these changes, SSR renders fall through to the useSyncExternalStore server snapshot and produce false; the real match is computed on the client after mount, as before.

Test coverage

Adds a regression test under @jest-environment node that renders both hooks via react-dom/server's renderToString. Without the fix, all three cases throw the original ReferenceError.

To make a Node-environment test runnable under the shared Jest config, the jsdom-only setup in packages/jest-preset-default/scripts/setup-globals.js and test/unit/{config,mocks} is guarded with typeof window !== 'undefined'. The @testing-library/user-event mock stays at the module top level (needed for babel-jest hoisting) and falls back to a passthrough when there is no DOM.

Use of AI Tools

Authored with assistance from Claude Code (Opus 4.7). All changes were reviewed and tested by the author.

@github-actions github-actions Bot added the [Package] Compose /packages/compose label May 27, 2026
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @xavier.lozano.carreras@a8c.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: xavier.lozano.carreras@a8c.com.

Co-authored-by: xavier-lc <xavilc@git.wordpress.org>
Co-authored-by: mcsf <mcsf@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@mcsf mcsf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks alright, thanks for the contribution!

SSR testing is sort of novel in Gutenberg, but I don't mind experimenting with some patterns here, especially to unblock SSR in a package like compose.

Comment thread packages/compose/src/hooks/use-viewport-match/index.js Outdated
@Mamaduka Mamaduka added [Type] Enhancement A suggestion for improvement. [Type] Bug An existing feature does not function as intended and removed [Type] Enhancement A suggestion for improvement. labels May 27, 2026
xavier-lc pushed a commit to xavier-lc/gutenberg that referenced this pull request May 27, 2026
Follow-up to the previous commit which updated the JSDoc per review
feedback on WordPress#78725.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@xavier-lc xavier-lc force-pushed the fix/compose-ssr-window-reference-error branch from d96b013 to 7059e80 Compare May 27, 2026 10:55
Xavier Lozano Carreras and others added 3 commits May 27, 2026 12:17
Both hooks declared `view = window` as a default parameter. Default expressions are evaluated unconditionally at every call, so the bare `window` identifier threw `ReferenceError: window is not defined` in non-DOM environments (Node SSR, jsdom-less tests) before the function body and the SSR-safe `useSyncExternalStore` server snapshot (`() => false`) had a chance to run.

Resolve the default lazily with `typeof window !== 'undefined' ? window : undefined` so the expression itself is safe, and let `getMQLSubscriber` accept `Window | undefined` and short-circuit to `EMPTY_SUBSCRIBER` when `view` is falsy.

Adds a `@jest-environment node` regression test that renders both hooks via `react-dom/server`'s `renderToString`. To make that runnable under the shared Jest config, the jsdom-only setup in `jest-preset-default` and `test/unit/config` is guarded with `typeof window !== 'undefined'` (the `@testing-library/user-event` mock stays at the module top level
for babel-jest hoisting and falls back to a passthrough when there is no DOM).

Regression from WordPress#76446.
Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com>
Follow-up to the previous commit which updated the JSDoc per review
feedback on WordPress#78725.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mcsf mcsf force-pushed the fix/compose-ssr-window-reference-error branch from 7059e80 to 6b820ed Compare May 27, 2026 11:17
@mcsf mcsf enabled auto-merge (squash) May 27, 2026 11:32
@mcsf mcsf merged commit c980422 into WordPress:trunk May 27, 2026
39 checks passed
@github-actions github-actions Bot added this to the Gutenberg 23.4 milestone May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Compose /packages/compose [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants