Today's newsletter is sponsored by ConfigCat — the feature flag service that helps you release features faster and with less risk. More on them at the end of today's newsletter.
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 }; }
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]);
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.
Are you looking for a service to support dynamic feature flags? ConfigCat is a cross-platform LaunchDarkly alternative that's easy to learn and quick to set up. With SDKs for 19 platforms — including JavaScript, Python, Ruby, Java, and even Rust — you can toggle features on or off without redeploying.
ConfigCat subscriptions are the same price regardless of your team size. Get 25% off any paid plan with code GO25
or just use ConfigCat's generous free tier. Definitely worth checking out.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] A custom React hook for watching media query changes"