ReactJS useMediaQuery hook using window.matchMedia(‘…’).
It is considered a bad practice to listen to the resize
event with JavaScript because it can lead to many UX issues, and it doesn’t consider more complex scenarios. Now with the window.matchMedia
API we can safely use media queries just like in CSS.
This is useful when in React (and other frameworks/libraries) you are forced to pass properties to a component that uses these to render it in a certain way. This makes it impossible to change the component with CSS only.
We can use the following React hook to listen to media queries and trigger a re-render to update the UI.
import { useEffect, useMemo, useState } from 'react'/**
* Check if a media query matches the UI
* @param {String} mediaQueryString
* @returns {Boolean}
*
* https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
* https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event
*
* Example:
* useMediaQuery('(max-width: 600px)');
* useMediaQuery('only screen and (min-width: 600px)');
* useMediaQuery('@media only screen and (min-width: 600px)');
*/
export function useMediaQuery(mediaQueryString) {
const queryString = removeReservedMediaKeyWord(mediaQueryString)
const query = useMemo(() => window.matchMedia(queryString), [queryString])
const [matches, setMatches] = useState(query.matches) // one-time, instantaneous check useEffect(() => {
const listener = (e) => setMatches(e.matches)
query.addEventListener('change', listener)
return () => query.removeEventListener('change', listener)
}, [query]) return matches
}function removeReservedMediaKeyWord(mediaQueryString) {
return mediaQueryString.replace('@media', '').trim()
}
Polyfill
You should install a polyfill just in case: https://www.npmjs.com/package/matchmedia-polyfill.
Testing in Jest
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
See: https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Related
Might also interest you; a list of media queries that I found useful with CSS in JS and are based on Bootstrap V5 breakpoint sizes:
https://pgarciacamou.medium.com/media-queries-with-css-in-jss-using-emotion-react-70bdb49d45c8