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.
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.
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.
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.
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.
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.
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.
// 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;
}
}
}
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.
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.
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