DEV Community

Cover image for How to use Media Query in React? | useMediaQuery
Anjan Shomodder
Anjan Shomodder

Posted on

How to use Media Query in React? | useMediaQuery

In this article, I will show you how to use Media Query in React using a custom hook.

Video Tutorial

We can use media queries in CSS to change style based on screen size.
But how can we do some operations based on screen size in JavaScript?

We can add a resize event listener to the window object.

window.addEventListener('resize', () => {
    // Do something
})
Enter fullscreen mode Exit fullscreen mode

But there's a better way to do this. What if we can use the CSS media query in JavaScript?
That will be great. And yes we can do that.

const mediaQuery = window.matchMedia('(min-width: 768px)')

console.log(mediaQuery.matches) // true or false
Enter fullscreen mode Exit fullscreen mode

We have to call the matchMedia method on the window object and pass the media query as an argument.
The returned mediaQuery object has a matches property which tells us if the media query matches or not.
But it will run only once. So we have to add a listener to the mediaQuery object.

const mediaQuery = window.matchMedia('(min-width: 768px)')

mediaQuery.addListener(mq => {
    if (mq.matches) {
        // Do something
    } else {
        // Do something
    }
})
Enter fullscreen mode Exit fullscreen mode

You need to pass a callback function to the addListener method. That function takes the mediaQuery object as an argument as we had before. This callback function will be called whether the media query matches or not. It will not run every time we change the screen size.

Let's use that in a custom hook.

Custom Hook

import React, { useState, useEffect } from 'react'

const breakPoints = {
    sm: '(min-width: 600px)',
    md: '(min-width: 800px)',
    lg: '(min-width: 1200px)',
    xl: '(min-width: 1600px)',
}

const useMediaQuery = breakpointName => {
    const [matches, setMatches] = useState(false)

    useEffect(() => {
        const value = breakPoints[breakpointName]

        const mq = window.matchMedia(value)

        setMatches(mq.matches)

        const handler = e => setMatches(e.matches)

        mq.addListener(handler)

        return () => {
            mq.removeListener(() => console.log('unmounted'))
        }
    }, [])

    return matches
}

export default useMediaQuery
Enter fullscreen mode Exit fullscreen mode
  • You can create a breakPoints object to store all of your breakpoints.
  • Then you can expect a breakpoint name as an argument.
  • We have the useEffect hook which will run once. So we can use it to add a listener to the mediaQuery object.
  • We will change the matches state whenever the media query matches or not.
  • When we will unmount the component, we will remove the listener.

Usage

import useMediaQuery from './useMediaQuery'

const MyComponent = () => {
    const isSmall = useMediaQuery('sm')

    return <div>{isSmall ? 'Small' : 'Large'}</div>
}
Enter fullscreen mode Exit fullscreen mode

Now you can use this media query to perform operations based on screen size.

Shameless Plug

I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it.
Please consider like this video and subscribe to my channel.

image

That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.

By the way, I am looking for a new opportunity in a company where I can provide great value with my skills. If you are a recruiter, looking for someone skilled in full-stack web development and passionate about revolutionizing the world, feel free to contact me. Also, I am open to talking about any freelance project. I am available on Upwork

Contacts

Videos might you might want to watch:





Discussion (2)

Collapse
lukeshiru profile image
Luke Shiru • Edited on

A few suggestions:

  • You should just take media queries directly instead of "sm" and stuff like that, because is not flexible enough and is not that useful either.
  • Instead of addListener and removeListener (both deprecated) you should use addEventListener and removeEventListener, with the "change" event.
  • You're not removing the listener when the cleanup of the effect is called.
  • Instead of creating the match media with every side effect, you could memoize it and make the mediaQuery a dependency of that memoized instance.
  • Instead of defaulting the state to false, you could default it to the current value of mq.matches.

Here's a version with those suggestions applied to it:

import { useEffect, useMemo, useState } from "react";

/** @param {string} mediaQuery */
export const useMatchMedia = mediaQuery => {
    const matchMedia = useMemo(
        () => window.matchMedia(mediaQuery),
        [mediaQuery],
    );
    const [matches, setMatches] = useState(matchMedia.matches);

    useEffect(() => {
        const changeHandler = ({ matches }) => setMatches(matches);
        matchMedia.addEventListener("change", changeHandler);

        changeHandler(matchMedia);

        return () => matchMedia.removeEventListener("change", changeHandler);
    }, [matchMedia]);

    return matches;
};
Enter fullscreen mode Exit fullscreen mode

And then...

import { useMatchMedia } from "./useMatchMedia.js";

const MyComponent = () => {
    const isSmall = useMatchMedia("(min-width: 600px)");

    return <div>{isSmall ? "Small" : "Large"}</div>;
};
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
thatanjan profile image
Anjan Shomodder Author

Thank you so much for sharing. Your solution is actually better. I am gonna use it from now on.