How to Create Responsive Layouts with Material UI and Next.js

July 1, 2021

One of the crucial aspects when building an application is to make it responsive. This feature allows the website or mobile app to adapt to different screen sizes or devices. This is because an individual can access the website via a mobile phone or computer.

In this article, we will learn how to create layouts that respond to different device widths and breakpoints. We will use Next.js and Material UI for the design.

Prerequisites

Before reading this article, you should have an understanding of:

  • How React works.
  • How data is passed as props in a React component.
  • How to fetch data from a remote API endpoint.
  • How to use media queries in CSS.

Objective

To create a responsive website using Material UI and Next.js. The application will retrieve test data from JSONPlaceholder which is a dummy API.

What is Material UI

Material UI is a React-based CSS utility framework that enables developers to create quality user interfaces. Material UI can be compared to Bootstrap but in a more advanced way.

Since it is a React-based CSS framework, it features numerous components that can be imported anywhere in a React application. Material UI has various use cases, ranging from layouts, styling inputs, navigation, etc. You can read more about the components in the official docs.

What is Next.js

Next.js is a front-end web framework based on React. It provides developers with loads of functionalities including Server Side Rendering and Static Site Generation. Next.js saves developers from the stress of setting up a react application from scratch. You can learn more about Next.js from here.

Next.js and Material UI setup

To make use of Material UI and Next.js, we need to install them as dependencies in our React application.

To install Next.js, use the command below.

Note that you need to have Node and NPM installed on your machine.

 npm install next react react-dom

We can then create a Next.js project using the following command:

npx create-next-app

To install Material UI, you can type the command below in your terminal:

npm install @material-ui/core

We can start the dev server using npm run dev command.

Creating the web-app

Before moving further, we need to familiarize ourselves with the project structure.

We will put all of our web pages in the pages folder. For instance, the pages/index.js will serve as the default home page.

Here is our project structure:

|--pages
|   |--index.js
|--components
|   |__Layout
|   |   | |--Header
|   |   | |--index.js
|   |   |--index.js
|   |--Card
|      |--index.js
|--container
|--index.js

The Layout folder in the components directory will render the app’s navigation bar. The Card folder will serve as the container component that gets mapped with the data retrieved from the remote API.

The card component serves as a reusable component that can be imported on any page in the app. It makes use of the React propTypes module to validate the type of data consumed by the component.

// Card.js
import React from "react";
import PropTypes from "prop-types";

const Card = ({ children, className, ...props }) => {
  return (
    <div className={`base-card ${className}`} {...props}>
      {children}
    </div>
  );
};

export default Card;

Card.propTypes = {
  children: PropTypes.node.isRequired,
};

PropType validation involves validating the type of data that gets passed to a React component as Props (Properties). If the conditions set in the propTypes object are not being met, JavaScript will throw an error. A perfect example is when we pass a number rather than a string as a prop.

Card.propTypes = {
  className: propTypes.string.isRequired; // the prop should be a string
}

The header component renders a simple text displaying the name of the app. This component is imported into the layout/index.js file.

Here is the code for the header component:

import React from "react";

const Header = () => {
  return (
    <header className={`header`}>
      <h1 className={`text-center`}>Awesome user profiles</h1>
    </header>
  );
};

export default Header;

In this project, the main Layout will only accept the Header component and the children props.

The children prop represents the remaining part of the UI. For instance, the card will display the data retrieved from the API.

The code for the Layout component is shown below:

import React from "react";
import Header from "./Header";
import PropTypes from "prop-types";

const Layout = ({ children }) => {
  return (
    <>
      <Header />
      {children}
    </>
  );
};

export default Layout;

Layout.propTypes = {
  children: PropTypes.node.isRequired, 
};

The Container component retrieves data from the API endpoint. This is, therefore, the perfect place to manage the state of the application.

Both the Container and Layout components are imported into the index page.

Let’s modify the Container component using Material UI. We will use the Grid component to allow the application to be responsive to all media breakpoints or screen sizes.

import React, { useState } from "react";
import Card from "../components/Card"; // Importing the card component
import Grid from "@material-ui/core/Grid";  //Importing the Grid component

const Container = () => {  
  const { profiles, setProfiles } = useState([]);

  const getProfiles = () => { //fetching data
    fetch("https://jsonplaceholder.typicode/users")
      .then((response) => {
        setProfiles(response.profiles); //updating state with new information
      })
      .catch((err) => console.log(JSON.stringify(err))); // catching any errors
  };

  useEffect(() => { 
    getProfiles();
  }, []);

  return (
    <section className={style.root}>
      <Grid container spacing={4}>
        {profile.map((users) => {
          return (
            <Grid
              xs={12}
              sm={6}
              lg={4}
              key={users.id}
              className={`cont-card`}
            >
              <Card>
                <div className={style.avatar}>
                  <img
                    src={`https://avatars.dicebear.com/v2/avataaars/{{${users.name}}}.svg?options[mood][]=happy`} //updating image
                  />
                </div>
                <div className={style.details}>
                  <h2 className={`text-center`}>{users.name}</h2>
                  <div className={style.userInfo}>
                    <p>{users.email}</p> // insert infor to card
                    <p>{users.phone}</p>
                    <p>
                      ${users.address.street} ${users.address.suite}
                    </p>
                    <p>
                      <a href={`https://${users.website}`}>{users.website}</a>
                    </p>
                    <p>{users.company.name}</p>
                  </div>
                </div>
              </Card>
            </Grid>
          );
        })}
      </Grid>
    </section>
  );
};

export default Container;

Let’s understand what the above code snippet does:

App state

const { profiles, setProfiles } = useState([]);

The snippet above manages the state of the application or the component. The profiles is an array that will hold data retrieved from the API.

Fetching data

const getProfiles = () => {
  fetch("https://jsonplaceholder.typicode/users")
    .then((response) => {
      setProfiles(response.profiles);
    })
    .catch((err) => console.log(JSON.stringify(err)));
};

The function getProfiles() initiates the process of retrieving data from the API. It relies on the Browser’s Fetch API. You can read more about the Fetch API from here

The getProfiles method is then called inside the useEffect() hook. The useEffect() performs all the lifecycle methods of a React component. This hook is triggered every time the browser reloads.

useEffect(() => {
  getProfiles(); // calls methods
}, []);

The purpose of the array [] inside the useEffect hook is to prevent the browser from fetching data from the API continuously. This process is referred to as “useEffect cleanup”. In our case, it will prevent the getProfiles method from been called multiple times.

Understanding the markup.

In the above Container component, you will notice the xs, sm, and lg props in the Grid component. These props are the breakpoints of the card component. They allow the card to be responsive.

 <Grid
      xs={12}
      sm={6}
      lg={4}        
      key={users.id}
      className={`cont-card`}
  >         
  • On extra small screens (xs), the card should occupy the full width of the device.

  • On small screen sizes (sm), the cards should occupy half of the screen width. Hence, allowing two cards to be arranged side by side on the webpage.

  • On large screen sizes (lg), the cards should occupy a specified part of four columns. This will allow three cards to be stacked side by side.

The container Component is then imported into the Home page container at pages/index.js.

import Layout from "../src/Layout";
import Container from "../src/Container";

export default function Home() {
  return (
    <div>
      <Layout>
        <Container />
      </Layout>
    </div>
  );
}

Conclusion

In this article, we have learned how to create a responsive website using Next.js and Material UI. You can, therefore, use this knowledge to create more quality applications.

Further Reading


Peer Review Contributions by: Wanja Mike


About the author

Caleb Olojo

Caleb is a Frontend developer and technical writer who loves sharing information around frontend web technologies and its best practices. In his free time, he contributes to opensource projects on GitHub.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.