article cover

How to Create a Custom useDeviceDetect() React Hook

Reed Bargerviews icon-
reactgatsby
reactgatsby

Hooks are great because they give you the tools to solve problems in your code. For example, an existing library may not have a feature you need.

I came across one such problem today that required the making a custom hook.

I’m in the process of building a new landing page for a course of mine, and I experienced a very strange error on mobile devices. On desktop computers, the styles looked great.

But when I looked at on mobile, everything was out of place and broken.

React App Error

I tracked the problem down to one library called react-device-detect which I was using to detect whether users had a mobile device or not. If so, I would remove the header.

// /templates/course.js
import React from "react";
import { isMobile } from "react-device-detect";
function Course() {
return (
<>
<SEO />
{!isMobile && <StickyHeader {...courseData} />}
{/* more components... */}
</>
);
}

The problem was that this library doesn’t have support for server-side rendering, which is what Gatsby uses by default. So I needed to create my own solution to check when a user’s on a mobile device. And for that, I decided to make a custom hook with the name useDeviceDetect.

Creating the Hook

I created a separate file for this hook in my utils folder with the same name, useDeviceDetect.js. Since hooks are just shareable JavaScript functions which leverage react hooks, I created a function called useDeviceDetect and imported React.

// utils/useDeviceDetect.js
import React from "react";
export default function useDeviceDetect() {}

Getting the User Agent from Window

The way that we can make sure whether we can get information about the user’s device is through the userAgent property (located on the navigator property of window).

And since interacting with the window API, as an API / external resource, would be classed as a side effect we need to get access to the user agent within the useEffect hook.

// utils/useDeviceDetect.js
import React from "react";
export default function useDeviceDetect() {
React.useEffect(() => {
console.log(`user's device is: ${window.navigator.userAgent}`);
// can also be written as 'navigator.userAgent'
}, []);
}

Once the component mounts we can use typeof navigator to determine if we are on the client or server. If we’re on the server, we won’t have access to the window. typeof navigator will be equal to the string undefined since it’s not there. Otherwise, if we’re on the client, we’ll be able to get our user agent property.

We can express all this using a ternary to get the userAgent data:

// utils/useDeviceDetect.js
import React from "react";
export default function useDeviceDetect() {
React.useEffect(() => {
const userAgent =
typeof navigator === "undefined" ? "" : navigator.userAgent;
}, []);
}

Checking if userAgent is a mobile device

userAgent is a string value which will be set to any one of the following device names if they are using a mobile device:

Android, BlackBerry, iPhone, iPad, iPod, Opera Mini, IEMobile, or WPDesktop.

All we have to do is take the string, we get and use the .match() method with a regex to see whether it’s any one of these strings. We’ll store it in a local variable called mobile.

We’ll store the result in state with the useState hook, which we’ll give an initial value of false. For it, we’ll create a corresponding state variable isMobile, and the setter will be setMobile.

// utils/useDeviceDetect.js
import React from "react";
export default function useDeviceDetect() {
const [isMobile, setMobile] = React.useState(false);
React.useEffect(() => {
const userAgent =
typeof window.navigator === "undefined" ? "" : navigator.userAgent;
const mobile = Boolean(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(mobile);
}, []);
}

So once we get the mobile value we will set it in state and then finally we will return an object from the hook so we can add more values in the future if we want to choose to add more functionality to this hook.

Within the object, we’ll add isMobile as a property and value:

// utils/useDeviceDetect.js
import React from "react";
export default function useDeviceDetect() {
const [isMobile, setMobile] = React.useState(false);
React.useEffect(() => {
const userAgent =
typeof window.navigator === "undefined" ? "" : navigator.userAgent;
const mobile = Boolean(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(mobile);
}, []);
return { isMobile };
}

Back in the landing page we can execute the hook and simply get that property from the destructured object and use it where we need.

// /templates/course.js
import React from "react";
import useDeviceDetect from "../utils/useDeviceDetect";
function Course() {
const { isMobile } = useDeviceDetect();
return (
<>
<SEO />
{!isMobile && <StickyHeader {...courseData} />}
{/* more components... */}
</>
);
}

So here’s a clear cut example of how hooks can give us the tools to fix our own problems when third-party libraries fall short.

Feel free to use this hook in your own code if you’re using a server-side rendered React framework like Gatsby or Next.js need to detect whether the user is on a mobile device. It wouldn’t be hard to extend this feature and make another state variable to check if the user is on a desktop.



course cover
Join The 2020 JS Bootcamp Video Course 🏕️
Code ArtistryWatch Now
Reed Barger
Professional JS developer who loves to write. Here to make you a better developer, faster.