This content originally appeared on Go Make Things and was authored by Go Make Things
A few weeks ago, I wrote about how to watch for media query changes with JavaScript.
Today, I wanted to share a little custom React hook you can use to detect media changes and automatically trigger a new render when it happens.
Let’s dig in!
An example
Let’s imagine you have a component with a video animation.
function AnimatedChart ({ src }) {
return (
<video
autoPlay={true}
muted={true}
loop={true}
src={src}
>
);
}
If the user has prefers-reduced-motion
enabled, you want to show a static image instead.
And, if they change their setting once the component is already in the DOM, you want to update it (either stopping the video, or adding it back in, accordingly).
Creating a custom React hook
We’ll start by importing useEffect()
and useState()
, as we’ll need both of them.
We’ll also create our hook, useWatchMatchMedia()
, and export
it. The hook accepts the match media selector
as an argument.
import { useEffect, useState } from 'react';
/**
* A hook to checks for matchMedia() settings and react to changes
* @param {string} selector The match media selector string
* @return {state} The state object
*/
export function useWatchMatchMedia (selector) {
// Code will go here...
}
Inside our hook, we’ll set an initial state of false
, and assign the state and setter function to the matches
and setMatches
variables, respectively.
We’ll return
the matches
state back out as an object property.
/**
* A hook to checks for matchMedia() settings and react to changes
* @param {string} selector The match media selector string
* @return {state} The state object
*/
export function useWatchMatchMedia (selector) {
// If true, user prefers reduced motion
const [matches, setMatches] = useState(false);
return { matches };
}
Setting matches
based on media query
Next, we’ll use useEffect()
to check for the actual media query value when the component is rendered. We’ll pass in the selector
as a dependency.
/**
* A hook to checks for matchMedia() settings and react to changes
* @param {string} selector The match media selector string
* @return {state} The state object
*/
export function useWatchMatchMedia (selector) {
// If true, user prefers reduced motion
const [matches, setMatches] = useState(false);
// On render, sets user preference and listens for changes
useEffect(() => {
// ...
}, [selector]);
return { matches };
}
We do this in useEffect()
instead of when setting our initial state because in some setups, the window
object used for the matchMedia()
method isn’t available when the component first loads.
Inside the useEffect()
callback function, we’ll pass our selector
into window.matchMedia()
, get the returned MatchMedia
object, and pass its .matches
property into setMatches()
to set the current state.
// On render, sets user preference and listens for changes
useEffect(() => {
const prefersReducedMotion = window.matchMedia(selector);
setMatches(prefersReducedMotion.matches);
}, [selector]);
Listening for query changes
We also want to update our matches
state when the user updates their settings or the query value changes.
We’ll create an updateMatches()
method that will run whenever there’s a change to the MatchMedia
object, and updates the matches
state. Then we’ll listen for change
events on the object, and pass the function in as a callback.
We’ll also return
a function that removes the event listener when the component is removed, so we don’t have unnecessary event listeners cluttering up browser memory.
// On render, sets user preference and listens for changes
useEffect(() => {
const prefersReducedMotion = window.matchMedia(selector);
setMatches(prefersReducedMotion.matches);
// Callback function for updating user preference
function updateMatches (event) {
setMatches(event.matches);
}
prefersReducedMotion.addEventListener('change', updateMatches);
return () => {
prefersReducedMotion.removeEventListener('change', updateMatches);
};
}, [selector]);
Using the useWatchMatchMedia()
hook in our component
Back in our <AnimatedChart />
component, we’ll import
the new useWatchMatchMedia()
hook.
Then we’ll run it, passing in (prefers-reduced-motion)
as the selector
. We’ll assign the returned matches
variable to a variable, and rename it prefersReducedMotion
for clarity.
function AnimatedChart ({ src }) {
const { prefersReducedMotion } = useWatchMatchMedia('(prefers-reduced-motion)');
return (
<video
autoPlay={true}
muted={true}
loop={true}
src={src}
>
);
}
Now, we can use the prefersReducedMotion
state to conditionally render a video or image. We’ll add a fallback
property to our component to use.
function AnimatedChart ({
src,
fallback
}) {
const { prefersReducedMotion } = useWatchMatchMedia('(prefers-reduced-motion)');
if (prefersReducedMotion) {
return (
<img
alt={fallback.alt}
src={fallback.src}
/>
)
}
return (
<video
autoPlay={true}
muted={true}
loop={true}
src={src}
>
);
}
Now, if the user has prefers-reduced-motion
enabled, or toggles it on after the component is rendered, they’ll get the fallback image. Otherwise, they’ll get the video.
Like this? A Lean Web Club membership is the best way to support my work and help me create more free content.
This content originally appeared on Go Make Things and was authored by Go Make Things

Go Make Things | Sciencx (2025-05-07T14:30:00+00:00) A custom React hook for watching media query changes. Retrieved from https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.