heroPhoto by XPS on Unsplash

Running client side only JavaScript with Gatsby.js

Gatsby is great for generating static sites, but sometimes we need to run some client-side only JavaScript as well. The question is how might we go about that? Well, that's what I'll be covering in this blog post. Actually, I've already written about something similar in a recent post Render client-side only component in Next.js. The approach is very similar, but I'll be walking through an example in this blog that is more specific to Gatsby.

What I tried to build

If you open up this blog on a mobile device or resize the window to a small enough width, you'd see the sandwich menu button appear. The idea is that when this button is clicked, an overlay would appear along with the navigation links.

Mobile menu navbar

Mobile menu overlay

The challenge is that, if I implement this in React, then Gatsby will try to render out the logic into HTML. Which means after the site has been generated, clicking on the menu button will not do anything.

There are two crucial factors to consider when trying to implement client-side only logic in Gatsby:

  1. Delaying any logic during "on-component-mount" stage of the life cycle until we are on the client-side.
  2. We need to be careful with any window or document references that only exist on the client-side.

The solution

I used useState to track the open/close state of the overlay. Since I wanted to disable the background scroll when the overlay is open as well, I ended up going with handler function handleMobileNavChange() to set the overflow value of <body> tag.

On clicking on the button, the value of mobileNavOpen will be set to true, and use can close the overlay by clicking on it which will set the state value to false.

const Header = () => {	
	const [mobileNavOpen, setMobileNavOpen] = useState(false);

	function handleMobileNavChange(value) {
        if (value) {
            document.body.style.overflow = "hidden";
        } else {
            document.body.style.overflow = originalStyle;
        }
        setMobileNavOpen(value);
    }

	return (
		<>
			<button type="button" onClick={() => handleMobileNavChange(true)}>Sandwich menu</button>
			<Overlay onClick={() => handleMobileNavChange(false)} />
		</>
	);
}

The value of originalStyle is set outside of the Header component by reading the value from DOM. It was to ensure the correct overflow value is fetched on the initial page load.

The typeof window !== 'undefined' check is to prevent Gatsby from erroring when generating the static site since window only exists on Browser and would error in Node environment.

const originalStyle = typeof window !== `undefined` && window.getComputedStyle(document.body).overflow;

const Header = () => { ... }

export default Header;

Final words

I have simplified the example above to make the explanation a bit simpler, if you are interested here is the code on Github.