By Andrei Popa on July 14, 2019 • 9 min read
Yes, there are probably thousands of how to’s on React Hooks, but in this article I’ll try to cover my own journey to get acquainted with this new technology. It is recommended to have a basic understanding of React before moving any further.
First of all, React Components - there are two ways of writing them - as functions or as classes.
Until recently, one would use a class when state changes were required, while keeping functions for presentational/dumb wrappers. Of course nobody would admit they use classes everywhere because they’re more convenient or because it’s an easier convention to follow. But functions are becoming cooler anyway, so they’re slowly taking over.
I’m not just saying it, it’s a fact that
onClick={handleClick}
reads better than onClick={this.handleClick.bind(this)}
What is it that makes functional components less respected? I believe that would be the lack of state management and the inexistent lifecycle. And this is where Hooks come into play.
If you want to learn more about the motivation behind the creation of React Hooks, this article should explain everything.
Let’s build a simple menu toggler and a homepage animation, slowly intoducing hooks along the way. And when I say simple, I mean inline-styles simple.
For convenience, I’ve used a basic Gatsby.js starter and created a Navigation class component.
src/components/navigation.js
import { Link } from "gatsby"
import React from "react"
class Navigation extends React.Component {
render() {
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page-2">Page 2</Link>
</li>
</ul>
</nav>
)
}
}
export default Navigation
Now for adding a toggle using classes and the standard state approach:
src/components/navigation.js
import { Link } from "gatsby"
import React from "react"
class Navigation extends React.Component {
constructor(props) {
super(props)
// the initial state
this.state = {
showMenu: false,
}
}
toggleActive = () => {
// toggling the state
this.setState({
showMenu: !this.state.showMenu,
})
}
render() {
return (
<nav>
{/* this is where the magic happens */}
<button onClick={() => this.toggleActive()}>Menu -></button>
<ul
style={{
display: this.state.showMenu ? `flex` : `none`,
}}
>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page-2">Page 2</Link>
</li>
</ul>
</nav>
)
}
}
export default Navigation
Preview in CodeSandbox. It looks prettier too, thank you inline-CSS!
Perfect, let’s refactor this code using functions!
src/components/navigation.js
const Navigation = () => {
return (
<nav>
...
</nav>
}
export default Navigation
At this point we lost the state functionality. Let’s use the state hook to bring it back
import { Link } from "gatsby"
import React, { useState } from "react"
const Navigation = () => {
const [showMenu, setShowMenu] = useState(false)
const toggleMenu = () => {
setShowMenu(!showMenu)
}
return (
<nav>
<button onClick={toggleMenu}>Menu -></button>
<ul
style={{
display: showMenu ? `flex` : `none`,
}}
>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page-2">Page 2</Link>
</li>
</ul>
</nav>
)
}
export default Navigation
Is this what all the hype is about? What is our new code doing?
showMenu
which acts as a getter and a setShowMenu
which is a setter function.showMenu
is passed to the useState
hook function as a param. If we need multiple states, the recommended way is to just create another useState
hook.If more complex state objects are required, it can be done as well, as detailed here: https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables
To me, the whole concept was not hard to understand and the code feels cleaner and more readable. And you don’t have to refactor classes to functions and functions to classes anymore every time a component changes scope.
Some argue this approach makes functions impure, which is true. So I guess using hooks depends a lot on a project’s implementation guidelines / best practice and what it’s trying to achieve. If it’s just a matter of readability, more complex functionality can be extracted in custom hooks (as we’re going to see below) and kept as separate concerns.
So far I couldn’t find a case where hooks failed to deliver the desired functionality, but feel free to change my mind with an example.
Getting back to our project, when we go from one page to another, we can see the state is lost. Let’s see what we can do to persist it.
Gatsby has it’s own internals to allow passing in props between routes (using - you guessed, Redux), but this post is not about Gatsby, so we’re going to create our own thing.
The idea is to write some code that pushes a key/value to localStorage. Or steal one from the internet, whatever is more convenient.
A great set of custom hooks can be found on https://usehooks.com
Let’s use it with our navigation component.
src/components/navigation.js
import { Link } from "gatsby"
import React from "react"
import useLocalStorage from "../hooks/useLocalStorage"
const Navigation = () => {
const [showMenu, setShowMenu] = useLocalStorage("showMenu", false)
const toggleMenu = () => {
setShowMenu(!showMenu)
}
return (
<nav>
<button onClick={toggleMenu}>Menu -></button>
<ul
style={{
display: showMenu ? `flex` : `none`,
}}
>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page-2">Page 2</Link>
</li>
</ul>
</nav>
)
}
export default Navigation
Now the menu toggle is persisted between pages and also on refresh, that’s great! So what exactly is our custom hook doing?
It has the exact same format as the useState
hook, being comprised of a getter and a setter, where the setter updates localStorage, while the getter fetches the value from localStorage:
src/hooks/useLocalStorage.js
const [storedValue, setStoredValue] = useState(() => {
// the getter
try {
// Get from local storage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.log(error)
return initialValue
}
})
// the setter
const setValue = value => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error)
}
}
return [storedValue, setValue]
In a nutshell, that’s state management with functional components and hooks.
Let’s consider the scenario where we want to create an animated component that must run some code after the component gets rendered for example. With classes one would use componentWillMount()
or componentDidMount()
. Let’s see what we can do with a function:
First, pass a prop to the header component from Layout
on the index page, then run some code if isIndex
is true.
Check out the code diff on Github | Preview in CodeSandbox
This implementation kind of works. Clicking the menu button shows the navigation items, it doesn’t break between pages, but is this really a good implementation in this case, does it have any flaws? For example if you click a menu item twice, the functional component gets called again, so the animation restarts. Let’s fix that.
We’re going to implement another hook, called useEffect
to fix the issues above and more.
src/components/header.js
import { Link } from "gatsby"
import PropTypes from "prop-types"
import React, { useEffect } from "react"
import anime from "animejs"
const Header = ({ siteTitle, isIndex }) => {
useEffect(() => {
if (isIndex) {
const timeline = anime.timeline({
easing: "easeOutQuad",
duration: 750,
})
timeline.add({
targets: ".header",
opacity: [0, 1],
})
timeline.add({
targets: ".headerTitle",
translateX: ["20%", "0"],
opacity: [0, 1],
})
}
})
return (
<header
className="header"
style={{
background: `rebeccapurple`,
marginBottom: `1.45rem`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 className="headerTitle" style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
{` `}
{isIndex ? `index page` : `secondary page`}
</Link>
</h1>
</div>
</header>
)
}
Header.propTypes = {
siteTitle: PropTypes.string,
isIndex: PropTypes.bool.isRequired,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header
We wrapped our animation in this function that’s going to be executed when the component gets mounted. Then, to fix this bug where clicking the same link twice restarts the animation, we’re just going to execute the code inside the hook only when isIndex
is changing.
Passing a second param to the hook (useEffect(() => { ... },[propertyName])
) is going to run that effect only when propertyName
gets updated. Pretty neat.
Another cool trick I found was using an empty array as a second argument to allow executing code only when component gets mounted, but not when it gets updated.
useEffect(() => { ... },[])
With a bit more tweaking we ended up with a togglable menu and an animated index page header, exactly what we were set out to do.
This is the most basic functionality you can get out of hooks. It only gets better when you want to fetch data, work with eventListeners or just want to switch to dark mode. One can now write functional components everywhere instead of toggling between class/functions and tap into state management or component lifecycle without maintaining two different code conventions in the same codebase.
The API offers other hooks, not just useState
and useEffect
. Check out the whole set here: https://reactjs.org/docs/hooks-reference.html.
If you have any questions or comments, or if you find this absolutely useless, please start a discussion using the section below. Thank you!