DEV Community

Cover image for How to Make Your React Apps Responsive with a Custom Hook
Francisco Mendes
Francisco Mendes

Posted on

How to Make Your React Apps Responsive with a Custom Hook

Overview

The hook I'm going to teach you today, despite being very simple, it's possible to do several things with it. For example if you want a certain component to have a certain behavior from a certain resolution, you can use this hook.

However, you can also make responsiveness adjustments with it in a simple and fast way.

The main reason I created this hook is that I needed to access the current width of the page, because I was using a calendar and I wanted it to adapt its behavior from a certain resolution because it was not responsive with the breakpoints from the css framework I was using.

And for that I decided to share with you the code of this hook as I will show you a simple example. Last but not least I will make the code for this example available in a github repository.

Let's code

Today I'm only going to install one dependency, but it's not mandatory, this is just to make my work in this example easier.

npm install classnames
Enter fullscreen mode Exit fullscreen mode

Now let's immediately start working on our hook.

// @src/hooks/useMediaQuery.js

const useMediaQuery = () => {
  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Our hook will take a single argument which will be the min width of the page, which will be our target.

// @src/hooks/useMediaQuery.js

const useMediaQuery = (minWidth) => {
  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Then we will import the useState() hook so that we can store two properties in our state, the windowWidth and the isDesiredWidth.

// @src/hooks/useMediaQuery.js
import { useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

The idea of windowWidth is to store the with of the current window, while isDesiredWidth has to be a boolean to validate that the window's current width is less than minWidth (our target).

Next, let's import the useEffect() hook to be aware of the changes that are made, which in this case is the change and validation of the width of the window.

// @src/hooks/useMediaQuery.js
import { useEffect, useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  useEffect(() => {
    // ...
  }, [state.windowWidth]);

  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Now we will have to create a function that will be our handler, which will be used whenever the window resizes.

// @src/hooks/useMediaQuery.js
import { useEffect, useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  useEffect(() => {
    const resizeHandler = () => {
      // ...
    };
    // ...
  }, [state.windowWidth]);

  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

In this handler we'll get the width of the current window, then we'll compare it with our minWidth and finally we'll store the data obtained in our state.

// @src/hooks/useMediaQuery.js
import { useEffect, useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  useEffect(() => {
    const resizeHandler = () => {
      const currentWindowWidth = window.innerWidth;
      const isDesiredWidth = currentWindowWidth < minWidth;
      setState({ windowWidth: currentWindowWidth, isDesiredWidth });
    };
    // ...
  }, [state.windowWidth]);

  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Now we have to create an event listener where whenever the window does a resize we want to invoke our resizeHandler function.

And if the window doesn't resize, we'll cleanup our useEffect() hook and remove the event listener.

// @src/hooks/useMediaQuery.js
import { useEffect, useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  useEffect(() => {
    const resizeHandler = () => {
      const currentWindowWidth = window.innerWidth;
      const isDesiredWidth = currentWindowWidth < minWidth;
      setState({ windowWidth: currentWindowWidth, isDesiredWidth });
    };
    window.addEventListener("resize", resizeHandler);
    return () => window.removeEventListener("resize", resizeHandler);
  }, [state.windowWidth]);

  // ...
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Then just return our isDesiredWidth so we can use the hook.

// @src/hooks/useMediaQuery.js
import { useEffect, useState } from "react";

const useMediaQuery = (minWidth) => {
  const [state, setState] = useState({
    windowWidth: window.innerWidth,
    isDesiredWidth: false,
  });

  useEffect(() => {
    const resizeHandler = () => {
      const currentWindowWidth = window.innerWidth;
      const isDesiredWidth = currentWindowWidth < minWidth;
      setState({ windowWidth: currentWindowWidth, isDesiredWidth });
    };
    window.addEventListener("resize", resizeHandler);
    return () => window.removeEventListener("resize", resizeHandler);
  }, [state.windowWidth]);

  return state.isDesiredWidth;
};

export default useMediaQuery;
Enter fullscreen mode Exit fullscreen mode

Now let's create the styles for our App.jsx module:

/* @src/App.module.css */

.layout {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
  background-color: #f1f6f9;
}

.layout h1 {
  color: #14274e;
}

.mobile {
  background-color: #394867 !important;
}

.mobile h1 {
  color: #f1f6f9;
}

.tablet {
  background-color: #14274e;
}

.tablet h1 {
  color: #9ba4b4;
}
Enter fullscreen mode Exit fullscreen mode

Now with our hook created and our App.jsx styles created, we can import both into the component. Then we'll use our hook to determine if it's mobile or tablet. In jsx we will do conditional rendering to have the desired visual result at each of the resolutions.

// @src/App.jsx
import React from "react";
import classNames from "classnames";

import styles from "./App.module.css";
import useMediaQuery from "./hooks/useMediaQuery";

const App = () => {
  const isMobile = useMediaQuery(768);
  const isTablet = useMediaQuery(1024);
  return (
    <div
      className={classNames([
        styles.layout,
        isMobile && styles.mobile,
        isTablet && styles.tablet,
      ])}
    >
      <h1>{isMobile ? "Mobile" : isTablet ? "Tablet" : "Desktop"}</h1>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

You should get a result similar to this:
final app

As promised at the beginning of the article, to access the github repository click here.

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 👹

Top comments (0)