Using React Hooks to Recognize & Respond to Current Viewport Size
By: Paige Niedringhaus
Tags: Web Development, JavaScript, Programming, React, Frontend Development

The Problem to Solve: A Pesky Full-Page Mobile Nav

As I said, my site had an odd bug where if the mobile nav menu was open and the browser got bigger, it didn’t disappear from view even though the React mobile nav component should have unmounted from the browser and disappeared from view.

Before I go into the details of my solution though, let me show you what I would like to happen on screen.

How it Should Work

In this scenario, what I would expect is when the user opened the mobile nav screen but the browser resized to the point where the mobile nav should no longer be displayed, that the mobile nav links would just disappear from view replaced with the across the top nav bar.

Like in this video below (this is my working site now).

This functionality is what I want to happen when the viewport resizes from mobile to desktop with the mobile nav open.

To accomplish this, I turned to React Hooks.

Note: If you like the animations I have for my mobile view where the hamburger transforms into an X as the links slide into view, I wrote an article about creating that here.

My Solution: useState and useEffect Hooks

In my app, I needed a way to:

Keep track of the viewport size when the app first loaded — to know whether to display the mobile nav hamburger icon versus the links across the top of the site, And know every time the viewport size changed, and if it was beyond the limits of when my mobile nav should be visible.

First, I Added Two New State Variables to the Navigation Component

Since my <Navigation> component is responsible for determining which navigation view to show to the user based on viewport size, it made sense to add a couple of state values there to track both if the mobile nav view is visible and if it is currently open or not.

Navigation.js

These are the state variables in the navigation component to track showing the mobile nav and if it’s open currently.

The mobileNavOpen state is in charge of knowing when to display the mobile nav links to a user.

The showMobileNavMenu state is responsible for determining whether to show the hamburger bar for the mobile menu or not.

Both are simple true / false booleans.

Next, I Added a useEffect to Track Initial Viewport Size

After creating the state variables, I needed to create a useEffect to check on component load if a user should be shown the large nav bar across the top or the more compact hamburger nav menu icon.

Navigation.js

useEffect Hook to determine which navigation view to show to the user on page load based on their initial browser size.

Luckily, I could directly access the viewport’s size using the Window Web API’s window.innerWidth method inside of a useEffect with an empty dependency array to tell it to only run once on page load. With this check, we can determine whether or not to show the mobile nav menu when the user first visits the site.

If the window’s innerWidth size is greater than 767px, the mobile nav menu doesn’t need to be displayed to the user, and if it is less than 767px, its hamburger icon will be shown.

Excellent! That’s the first piece of the puzzle solved. On to the next issue: tracking whenever the viewport size changes.

Finally, Track All Other Browser Size Changes Inside a Second useEffect

The other thing to solve for was: any time the browser resized, did I need to continue showing the mobile nav icon (and the mobile nav links screen if it was open)?

Time to employ a second React useEffect inside of the <Navigation> component.

Navigation.js

This useEffect adds an event listener on component mount that will watch for window resizes and fire the handleResize function accordingly.

In this useEffect , I created a function named handleResize() which is responsible for setting my local state variables of showMobileNavView and mobileNavOpen to true or false based on current viewport size.

After defining the function, I then used the window object again, but this time I added an event listener to it, keeping track of the resize event. Whenever the browser detects a change in the window’s size, it will run the handleResize function and based on the window.innerWidth ’s value, react accordingly. (Excuse the pun! 😬)

If the window’s innerWidth size exceeded 767px, both the mobile nav icon and the mobile nav view (regardless of if it’s currently open or not), will be set to false , and whenever it’s less than 767px, the mobile nav icon should be visible (notice I don’t dictate if it should be open though — if the user wants to open or reopen it, they can choose to do so).

And of course, on component unmount (which in this case won’t happen until someone leaves the site because the <Navigation> component is omnipresent across the site), a cleanup function is fired to remove the event listener from the window object.

I gave this other useEffect an empty dependency array as well, so it would only create the event listener once when the component first mounts.

Could I have combined the two useEffect functions into one since both should only run once? Yes. But one of the driving factors behind React Hooks was to be able to more cleanly separate out unrelated code logic that was previously bound together by needing to run in the same lifecycle components. There’s no harm in having two useEffect functions with the same empty dependency array, and if one ever needs to be moved or refactored, it can be done easily without impacting the other.