React – Using the useMediaQuery hook to make the number of columns in a Material UI GridList responsive

In this short tutorial: How I used React’s useMediaQuery hook to make the number of columns in a Material GridList responsive.

Let’s say you’re using Material UI’s GridList, but you want the number of columns it uses to change depending on how wide the user’s screen is, like this:

Poking around the API reveals that you can set the number of columns by setting the col property to a value, but it doesn’t say how to make it responsive.

If you Google this problem, you might come across this StackOverflow post where the top-rated reply suggests using withWidth, but the React docs have widthWidth tagged as deprecated.

Instead, they point you at the useMediaQuery hook, which is what my example will use.

Being somewhat new to React hooks, my first instinct was to put this logic into a function but React didn’t like that. The useMediaQuery hook has to sit inside the functional component, and not inside a useEffect, or a function, or any of the other things I thought I should reach for. Also, it returns a boolean, so it’s better to think of it as setting a flag that you then check somewhere else.

Eventually, I arrived at this approach which sets a series of flags to true/false and then checks them to determine how many columns to use:

import React from 'react';
import { useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

interface IInbox {
  posts: Array<Post>; // array of type "Post"
}

const Inbox: React.FC<IInbox> = ({ posts }) => {

  const theme = useTheme();

  const screenExtraLarge = useMediaQuery(theme.breakpoints.only('xl'));
  const screenLarge = useMediaQuery(theme.breakpoints.only('lg'));
  const screenMedium = useMediaQuery(theme.breakpoints.only('md'));
  const screenSmall = useMediaQuery(theme.breakpoints.only('sm'));
  const screenExtraSmall = useMediaQuery(theme.breakpoints.only('xs'));
  const screenNarrow = useMediaQuery('(max-width:340px)');

  const getScreenWidth = () => {
    if (screenExtraLarge) {
      return 6;
    } else if (screenNarrow) {
      return 1;
    } else if (screenLarge) {
      return 5;
    } else if (screenMedium) {
      return 4;
    } else if (screenSmall) {
      return 3;
    } else if (screenExtraSmall) {
      return 2;
    } else {
      return 3;
    }
  }

  // note: I skipped some of the structural elements of my return statement to focus on just what's relevant to this example

  return (
    <>
    {posts.length === 0 &&
       <span>No posts!</span>
    }

    {posts.length > 0 &&
       <GridList 
         cols={getScreenWidth()}
         spacing={2}
         id="grid-list-inbox"
       >
       {posts.map((post, index: number) => (
         <GridListTile
           id={`grid-list-tile-${post.pid}`}
           className="inboxLetter"
           key={post.pid}
           onClick={() => pressEnvelope(post)}
           rows={1.25} >
             <img src="path/to/image.png"/>
             <GridListTileBar
               title={"From: " + post.from}
               className="inboxTitle"
             </GridListTile>
         ))} // closes .map 
         </GridList>
    }
    </>
  )
}


export default Inbox;

As the screen is resized, the col property runs the method and gets the correct number of columns to use.

If I find a more elegant way to do this I’ll update this post, but in the meantime, I hope it helps someone else on their journey with React useMediaQuery!