A custom React hook for watching media query changes

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.


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


Print Share Comment Cite Upload Translate Updates
APA

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/

MLA
" » A custom React hook for watching media query changes." Go Make Things | Sciencx - Wednesday May 7, 2025, https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/
HARVARD
Go Make Things | Sciencx Wednesday May 7, 2025 » A custom React hook for watching media query changes., viewed ,<https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/>
VANCOUVER
Go Make Things | Sciencx - » A custom React hook for watching media query changes. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/
CHICAGO
" » A custom React hook for watching media query changes." Go Make Things | Sciencx - Accessed . https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/
IEEE
" » A custom React hook for watching media query changes." Go Make Things | Sciencx [Online]. Available: https://www.scien.cx/2025/05/07/a-custom-react-hook-for-watching-media-query-changes/. [Accessed: ]
rf:citation
» A custom React hook for watching media query changes | Go Make Things | Sciencx | 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.

You must be logged in to translate posts. Please log in or register.