React Hooks I (finally) Understand

By Andrei Popa on July 14, 20199 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

  • functions are more performant
  • onClick={handleClick} reads better than onClick={this.handleClick.bind(this)}
  • the simplest implementation possible for taking props and returning HTML is a function
  • functions transpile to less code (over 20% or more) than classes

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.

State management

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

Preview in CodeSandbox

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

Preview in CodeSandbox

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

Preview in CodeSandbox

Is this what all the hype is about? What is our new code doing?

  • we have a showMenu which acts as a getter and a setShowMenu which is a setter function.
  • the initial value for 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

Preview in CodeSandbox

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.

Component lifecycle

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

Preview in CodeSandbox

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.

Conclusion

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!