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:
With that, we should be good to go.
↓ Here's a great React course we recommend. Plus, this affiliate banner helps support the site 🙏
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!