Implement a useMediaQuery hook that subscribes and responds to media query changes (e.g. screen size, resolution, orientation, etc.).
export default function Component() {const isSmallDevice = useMediaQuery('only screen and (max-width: 768px)');return <div>{isSmallDevice && <a href="#">Menu</a>}</div>;}
Hint: The window.matchMedia API would be helpful.
query: string: The media query to match. It must be a valid CSS media query stringThe hook returns a boolean value that indicates whether the media query is a match.
useMediaQuery(query) returns whether the browser currently matches a media query string. This hook is really a bridge to an external browser store owned by matchMedia, so the core jobs are reading the current snapshot, subscribing to changes, and cleaning up correctly.
window.matchMedia(query) is a JavaScript method that checks if the document matches a given CSS media query. It returns a MediaQueryList object, which provides:
.matches (boolean): Indicates whether the document currently matches the media query.addEventListener("change", callback): Listens for changes in the media query’s match state.removeEventListener("change", callback): Removes the event listener when it’s no longer neededExample:
const mediaQuery = window.matchMedia('(max-width: 768px)');if (mediaQuery.matches) {console.log('Viewport is 768px or smaller');}mediaQuery.addEventListener('change', (e) => {console.log(e.matches ? 'Now small screen' : 'Now large screen');});
useMediaQuery is a hook that allows a React app to leverage this browser API and respond to media changes (e.g. screen size, resolution, orientation, and more).
No matter which React primitive you choose, the hook needs the same three pieces:
mediaQueryList.matches)useEffect and useStateThe useMediaQuery hook can be implemented using useState to store the current match and useEffect to update that state when the media query changes. We attach a listener to the change event on the MediaQueryList object returned by window.matchMedia(...).
import { useEffect, useState } from 'react';/*** @param {string} query* @returns {boolean}*/export default function useMediaQuery(query) {// Read the current snapshot immediately so the first render matches the browser state.const [matches, setMatches] = useState(() => window.matchMedia(query).matches,);useEffect(() => {// Recreate the MediaQueryList for the current query and clean up its matching listener.const mediaQueryList = window.matchMedia(query);function updateMatch() {setMatches(mediaQueryList.matches);}mediaQueryList.addEventListener('change', updateMatch);return () => {mediaQueryList.removeEventListener('change', updateMatch);};}, [query]);return matches;}
useSyncExternalStoreReact's useSyncExternalStore hook is an even better fit because matchMedia is really an external store owned by the browser. useSyncExternalStore makes the subscription and snapshot model explicit, which is exactly what this hook needs.
import { useCallback, useSyncExternalStore } from 'react';export default function useMediaQuery(query: string): boolean {// useSyncExternalStore expects a subscribe function that removes the exact listener it adds.const subscribe = useCallback((callback: () => void) => {const mediaQueryList = window.matchMedia(query);mediaQueryList.addEventListener('change', callback);return () => {mediaQueryList.removeEventListener('change', callback);};},[query],);// matchMedia is the external store; `.matches` is the current snapshot.return useSyncExternalStore(subscribe,() => window.matchMedia(query).matches,);}
change listener that was registered for the current query.console.log() statements will appear here.