Introduction
I've made a few small apps with React and this blog is made with Gatsby (for now), so I know my way around a bit. But right around the time React introduced hooks, I was busy doing other things. The other day I thought "gee... it'd be nice to look at that hooks thing again." So I decided to dig in and build a small toggleable menu component. In this article I'll show you how both the useState
and useEffect
hooks work. You can see also see a finished version of the toggle on CodeSandbox.
The Markup
It often helps me to figure out what the markup for a component will be before I start to tinker with functionality. In this case, what I want is
- a button
- with a sibling menu
- with a number of list items
<div>
<button>
Toggle
</button>
<ul>
<li>
<a href="/some-link">Some Link</a>
</li>
<!-- more menu items -->
</ul>
</div>
To turn this markup into a basic react component, we can do this -
import React from 'react'
const Toggle = () => (
<div>
<button>
Toggle
</button>
<ul>
<li>
<a href="/some-link">Some Link</a>
</li>
<!-- more menu items -->
</ul>
</div>
)
export default Toggle
In terms of functionality,
- the menu will start hidden
- it can be opened by clicking on the button
- it can be closed by clicking on the button, the document, or with the escape key
Believe it or not, React hooks make the functionality pretty quick to get started.
The useState
Hook
As the React documentation states
useState
is a Hook that lets you add React state to function components.
This means that instead of turning the component into a class, we can keep the same component as before and then add state to it.
The convention is (seems to be?) to set a variable as an array equal to useState
with the initial value.
const [ state, setState ] = useState(initialValue)
In this case, state
will change from the initialValue
to whatever the updated state will be. setState
is a function that will be changed to update the state
value.
In this case, I want a toggle, so I want to make state be called open
, setState
will be setOpen
and useState
will take an initial value of false
(the menu will be closed to start).
The button will then take a click handler which calls setOpen
and sets its argument equal to the oposite of the initial value.
import React, { useState } from 'react'
const Toggle = () => {
const [ open, setOpen ] = useState(false)
return (
<div>
<button onClick={ () => setOpen(!open) }>
Toggle
</button>
<ul>
<li>
<a href="/some-link">Some Link</a>
</li>
<!-- more menu items -->
</ul>
</div>
)
}
export default Toggle
Now that I've got a true
/false
value set up, I can use that to manage aspects of the component.
import React, { useState } from 'react'
const Toggle = () => {
const [ open, setOpen ] = useState(false)
// assumes that show and active are classes for css
return (
<div>
<button
aria-expanded={open === true ? "true" : "false"}
className={open === true ? "active" : ""}
onClick={ () => setOpen(!open) }>
Toggle
</button>
<ul className={open ? "show" : ""}>
<li>
<a href="/some-link">Some Link</a>
</li>
<!-- more menu items -->
</ul>
</div>
)
}
export default Toggle
The useEffect
Hook
Next, I want to add some event listeners to the document. This way if someone clicks off the open menu or hits the escape key, the menu will close.
To do that, I create handler functions. The useEffect
hook can then return a function to clean up the event handler when it's done. This removes the handler from the document so that it doesn't interfere with any other handlers.
import React, { useState, useEffect } from 'react'
const Toggle = ({ id }) => {
const [ open, setOpen ] = useState(false)
// monitor the state of the toggle
// add/remove click event handler to the document
useEffect(() => {
const clickHandler = ({ target }) => {
const container = document.getElementById(`container-${id}`);
if (container.contains(target)) return;
setOpen(false);
};
document.addEventListener("click", clickHandler);
// these functions clean up the event listeners
return () => document.removeEventListener("click", clickHandler);
});
// same but for keypresses
// if the esc key is pressed close the toggles
useEffect(() => {
const keyHandler = ({ keyCode }) => {
if (keyCode !== 27) return;
setOpen(false);
};
document.addEventListener("keydown", keyHandler);
return () => document.removeEventListener("keydown", keyHandler);
});
// assumes that show and active are classes for css
return (
<div id={id}>
<button
aria-expanded={open === true ? "true" : "false"}
className={open === true ? "active" : ""}
onClick={ () => setOpen(!open) }>
Toggle
</button>
<ul className={open ? "show" : ""}>
<li>
<a href="/some-link">Some Link</a>
</li>
<!-- more menu items -->
</ul>
</div>
)
}
export default Toggle
Conclusion
The finished example shows how to abstract the menu itself into a separate component. That way you can create multiple menus by passing it an array of menu objects.
It took a lot less time than I thought to learn how to use these hooks. The next time I need to make a React project I'll definitely use these.