NextJs Testing: window.matchMedia is not a function in use-mobile hook


While writing tests for my Next.js app using Vitest and React Testing Library, I ran into an annoying error that looked like this:
TypeError: window.matchMedia is not a function
❯ hooks/use-mobile.ts:12:24
The error was pointing to this line inside my use-mobile.tsx
hook:
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
This hook helps determine if the user is on a mobile device using matchMedia
. It works perfectly in the browser—but crashes in the test environment because window.matchMedia
isn’t defined in JSDOM (the environment Vitest uses for running tests).
🛠️ The Fix: Add a Defensive Check
The quick fix was to wrap the entire useEffect
logic in a condition that checks if window
and window.matchMedia
exist. This avoids calling matchMedia
during tests (or in non-browser environments like SSR).
❌ Before (Fails in Tests)
useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
✅ After (Test-Safe)
useEffect(() => {
if (typeof window !== "undefined" && window.matchMedia) {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}
}, []);
This makes your hook resilient both in tests and during server-side rendering (SSR).
💡 Bonus Tip: Mock matchMedia
in Your Test Setup
Alternatively (or additionally), you can mock matchMedia
in your test setup file:
// setup.ts (or setupTests.ts)
beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query) => ({
matches: false,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
}),
});
});
This ensures your tests don’t break even if matchMedia
is called. But I still recommend the typeof window !== "undefined"
check in your hook, just to be safe in SSR contexts.
🧵 Wrap-up
If you're working with matchMedia
in a React hook, always remember that test environments and SSR might not have the window
object or browser APIs like matchMedia
. Adding a simple check avoids runtime errors and keeps your tests green. 🌱
If you found this helpful or ran into a similar issue, let me know! I'd love to hear how you handled it.
Subscribe to my newsletter
Read articles from Lateef Quadri Olayinka directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Lateef Quadri Olayinka
Lateef Quadri Olayinka
I am AbdQaadir by name. A Nigeria based Front End Developer. I'm interested in Software Engineering and Cyber Security.