Tutorial

Using React Hooks in Gatsby

Published on January 22, 2020
author

Juan Villela

Using React Hooks in Gatsby

JavaScript treats functions as first-class citizens. And we can see this in React now more than ever with the introduction of Hooks in version 16.8. They allow for state manipulation and side-effects on functional components.

At its core, Gatsby uses vanilla React with all its features. So this means Hooks are available to use with a simple import statement. Let’s take a look at some of the ways we can take advantage of them.

Getting Started

There isn’t anything, in particular, we’ll need to install. However, it’s necessary to have the latest version of React and Gatsby or at least v16.8+. We can do so by checking out our package.json and finding which version we already have installed.

If you need to upgrade, we can run the following:

$ npm install react@16.8.0 react-dom@16.8.0
# or
$ yarn add react@16.8.0 react-dom@16.8.0

With that, we should be good to go.

Using Hooks

Let’s set up a header.js component with a scrolled state and a dropdown menu.

Our component will be a fixed header at the top that remains in place while the user scrolls through the page, but displays a box-shadow when the user isn’t at the top. That means our state would be a boolean which toggles based on the current window position. We’ll use native APIs to determine window position.

src/components/header.js
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';

const Header = () => {
  // determined if page has scrolled and if the view is on mobile
  const [scrolled, setScrolled] = useState(false);

  // change state on scroll
  useEffect(() => {
    const handleScroll = () => {
      const isScrolled = window.scrollY > 10;
      if (isScrolled !== scrolled) {
        setScrolled(!scrolled);
      }
    };

    document.addEventListener('scroll', handleScroll, { passive: true });

    return () => {
      // clean up the event handler when the component unmounts
      document.removeEventListener('scroll', handleScroll);
    };
  }, [scrolled]);

  return (
    <header data-active={scrolled}>
      <Link to="/">React Hooks on Gatsby</Link>
      <nav>
        <Link to="/about/">About</Link>
        <Link to="/contact/">Contact Us</Link>
      </nav>
    </header>
  );
};

export default Header;

The window.scrollY property returns the number of pixels that have passed vertically on scroll. We compare that value to 10 pixels and we get a boolean value that will tell us if the user has moved the document or not. We then wrap the conditional property around a function that updates the scrolled state whenever the user scrolls through the site. This function is then passed to an event listener on the document.

All of this will live inside the useEffect hook, which will return a removeEventListener on the document to clean up the event handler when the component unmounts. The useEffect hook allows us to perform side effects on our component. The effect will fire after every completed render by default, however, we can pass a second argument as an array of values on which the effect depends on to fire. In our case, [scrolled].

With that, we can add an identifying attribute to our HTML to determine the state of the element. We’ll use a data-active attribute with the boolean from the scrolled state. And in our CSS, we can add the box-shadow effect using the attribute selector.

src/styles/main.scss
header {
  position: fixed;
  top: 0;
  transition: box-shadow .3s ease;
  width: 100%;

  &[data-active='true'] {
    box-shadow: 0 2px 8px rgba(152,168,188,.2);
  }
}

The same styling can be used with styled-components. Swapping the header selector with the component’s tagged template literal will provide the same functionality.

Hooks and User Input

We’ll take this example a step further and add a dropdown menu accessible via a toggle button. We can keep most of what was already made and just modify the state change properties. The property will be renamed to state and setState while taking an object with our various state variables.

Updating state would be a little different in this case. First, we need to pass the previous state as a spread operator, followed by the updated value. This is because, unlike class components, functional ones will replace updated objects instead of merging them.

src/components/header.js
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';

import Dropdown from './dropdownMenu';

const Header = () => {
  // determined if page has scrolled and if the view is on mobile
  const [state, setState] = useState({
    scrolled: false,
    visible: false,
  });

  // change state on scroll
  useEffect(() => {
    const handleScroll = () => {
      const isScrolled = window.scrollY > 10;
      if (isScrolled !== state.scrolled) {
        setState({
          ...state,
          scrolled: !state.scrolled,
        });
      }
    };
    document.addEventListener('scroll', handleScroll, { passive: true });
    return () => {
      // clean up the event handler when the component unmounts
      document.removeEventListener('scroll', handleScroll);
    };
  }, [state.scrolled]);

  // toggle dropdown visibility
  const toggleVisibility = () => {
    setState({
      ...state,
      visible: !state.visible,
    });
  };

  return (
    <header data-active={state.scrolled}>
      <Link to="/">React Hooks on Gatsby</Link>
      <nav>
        <Link to="/about/">About</Link>
        <Link to="/contact/">Contact Us</Link>
        <button onClick={toggleVisibility} type="button">
          Solutions
        </button>
        <Dropdown
          aria-hidden={!state.visible}
          data-active={state.visible}
        />
      </nav>
    </header>
  );
};

export default Header;

We want to have the user click a button that will open up an additional menu. When the Solutions button is clicked, it’ll toggle the visible boolean. This boolean is passed to the aria-hidden and data-active attributes for use in our CSS.

src/styles/main.scss
// the section element is our <Dropdown /> component

header {
  top: 0;
  transition: box-shadow .3s ease;

  &[data-active='true'] {
    box-shadow: 0 2px 8px rgba(152,168,188,.2);
  }

  &,
  section {
    position: fixed;
    width: 100%;
  }

  nav,
  section {
    overflow: hidden;
  }

  section {
    height: 0;
    left: 0;
    opacity: 0;
    right: 0;
    top: 5.5rem;
    transition: all .3s ease-in-out;
    visibility: hidden;

    &[data-active='true'] {
      height: auto;
      opacity: 1;
      visibility: visible;
    }
  }
}

Conclusion

With Hooks, we get all the benefits of class components with the familiarity of functional ones. Gatsby takes full advantage of that. I recommend you take a look at all the Hooks available in the React documentation. And you can even dive into building your own hooks!

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Juan Villela

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Hi Juan. This approach can be used in development mode only. In production will throw an error as the window object does not exist in the server. There are some solutions for this issue as for example using loadable-components library https://www.npmjs.com/package/react-loadable Regards

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.