Using React Hooks in Gatsby

Juan Villela

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!

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#