Primer on React Hooks

Last updated February 27th, 2019

React is great, but there are some slightly aggravating architecture principles that need to be addressed while developing larger applications. For instance, using complex higher order components to re-use stateful logic and relying on lifecycle methods that trigger unrelated logic. Well, React hooks help ease that aggravation... let's get into it.

This is part one of a two-part series:

  1. (this article) Primer on React Hooks
  2. React Hooks - A deeper dive featuring useContext and useReducer

Contents

Learning Objectives

By the end of this article, you will be able to answer the following questions:

  1. What are hooks?
  2. Why were they implemented in React?
  3. How are hooks used?
  4. Are there rules to using hooks?
  5. What is a custom hook?
  6. When should I use custom hooks?
  7. What are the benefits of using custom hooks?

What are hooks?

Hooks allow you to:

  1. Use state and "hook into" the lifecycle methods in functional components.
  2. Re-use stateful logic between components, which simplifies component logic and, best of all, let's you skip writing classes.

Why were they implemented in React?

If you've used React, you know how moody complex, stateful logic can get, right? This happens when applications get several new features added to up the functionality. To try and simplify this problem, the big brains behind React sought to find a way around this issue.

  1. Re-use stateful logic between components

    Hooks allow developers to write simple, stateful, functional components and spend less time designing and restructuring component hierarchy while developing. How? With hooks, you can extract and share stateful logic between components.

  2. Simplifies component logic

    When the inevitable exponential growth of logic appears in your application, simple components become a dizzying abyss of stateful logic and side effects. Lifecycle methods become cluttered with unrelated methods. A component's responsibilities grow and become inseparable. In turn, this makes coding cumbersome and testing difficult.

  3. You can skip classes

    Classes are a huge part of React's architecture. There are many benefits to classes, but they create a barrier to entry for beginners. With classes, you also have to remember to bind this to event handlers, and so code can become lengthy and a bit redundant. The future of coding will also not play nicely with classes as they might encourage patterns that tend to slack behind other design patterns.

How are hooks used?

Hooks are available in React version 16.8.

import { useState, useEffect } from 'react';

Simple enough, but how do you actually use these new methods? The following examples will be quite simple, but the abilities of these methods are very powerful.

The useState hook method

The best way to use the state hook is to destructure it and set the original value. The first parameter will be used to store the state, the second to update the state.

For example:

const [weight, setWeight] = useState(150);

onClick={() => setWeight(weight + 15)}
  1. weight is the state
  2. setWeight is a method used to update the state
  3. useState(150) is the method used to set the initial value (any primitive type)

It's worth noting that you can destructure the state hook many times in a single component:

const [age, setAge] = useState(42);
const [month, setMonth] = useState('February');
const [todos, setTodos] = useState([{ text: 'Eat pie' }]);

So, the component might look something like:

import React, { useState } from 'react';

export default function App() {
  const [weight, setWeight] = useState(150);
  const [age] = useState(42);
  const [month] = useState('February');
  const [todos] = useState([{ text: 'Eat pie' }]);

  return (
    <div className="App">
      <p>Current Weight: {weight}</p>
      <p>Age: {age}</p>
      <p>Month: {month}</p>
      <button onClick={() => setWeight(weight + 15)}>
        {todos[0].text}
      </button>
    </div>
  );
}

The useEffect hook method

It's best to use the effect hook like you would any common lifecycle method like componentDidMount, componentDidUpdate, and componentWillUnmount.

For example:

// similar to the componentDidMount and componentDidUpdate methods
useEffect(() => {
  document.title = `You clicked ${count} times`;
});

Anytime the component updates, useEffect will be called after render. Now, if you only want useEffect to update when the variable count is mutated, you simply add that fact to the end of the method in an array, similar to the accumulator at the end of the higher-order reduce method.

// check out the variable count in the array at the end...
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [ count ]);

Let's combine the two examples:

const [weight, setWeight] = useState(150);

useEffect(() => {
  document.title = `You weigh ${weight}, you ok with that?`;
}, [ weight ]);

onClick={() => setWeight(weight + 15)}

So, when onClick is triggered, the useEffect method will also be called and render the new weight in the title of the document just slightly after the DOM updates.

Example:

import React, { useState, useEffect } from 'react';

export default function App() {
  const [weight, setWeight] = useState(150);
  const [age] = useState(42);
  const [month] = useState('February');
  const [todos] = useState([{ text: 'Eat pie' }]);

  useEffect(() => {
    document.title = `You weigh ${weight}, you ok with that?`;
  });

  return (
    <div className="App">
      <p>Current Weight: {weight}</p>
      <p>Age: {age}</p>
      <p>Month: {month}</p>
      <button onClick={() => setWeight(weight + 15)}>
        {todos[0].text}
      </button>
    </div>
  );
}

useEffect is perfect for making API calls:

useEffect(() => {
  fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then(results => results.json())
    .then((data) => { setTodos([{ text: data.title }]); });
}, []);

React hooks look great, but if you take a minute, you may realize that re-initializing multiple hook methods, such as useState and useEffect, in multiple components could slight the DRY principle we all cherish. Well, lets see how we can re-use these wonderful new built-in methods by creating custom hooks. Before we dive into some new tricks involving hooks, we’ll look at the rules using hooks and make our custom hook journey more enjoyable.

Are there rules to using hooks?

Yes, React hooks have rules. These rules may seem unconventional at first glance, but once you understand the basics of how React hooks are initiated the rules are quite easy to follow. Plus, React has a linter to keep you from breaking the rules.

(1) Hooks must be called in the same order, at the top level.

Hooks create an array of hook calls to keep order. This order helps React tell the difference, for example, between multiple useState and useEffect method calls in a single component or across an application.

For example:

// This is good!
function ComponentWithHooks() {
  // top-level!
  const [age, setAge] = useState(42);
  const [month, setMonth] = useState('February');
  const [todos, setTodos] = useState([{ text: 'Eat pie' }]);

  return (
      //...
  )
}
  1. On first render, 42, February, [{ text: 'Eat pie'}] are all pushed into a state array.
  2. When the component re-renders, the useState method arguments are ignored.
  3. The values for age, month, and todos are retrieved from the component's state, which is the aforementioned state array.

(2) Hooks cannot be called within conditional statements or loops.

Because of the way that hooks are initiated, they are not allowed to be used within conditional statements or loops. For hooks, if the order of the initializations changes during a re-render, there is a good chance your application will not function properly. You can still use conditional statements and loops in your components, but not with hooks inside the code blocks.

For example:

// DON'T DO THIS!!
const [DNAMatch, setDNAMatch] = useState(false)

if (name) {
  setDNAMatch(true)
  const [name, setName] = useState(name)

  useEffect(function persistFamily() {
    localStorage.setItem('dad', name);
  }, []);
}
// DO THIS!!
const [DNAMatch, setDNAMatch] = useState(false)
const [name, setName] = useState(null)

useEffect(() => {
  if (name) {
    setDNAMatch(true)
    setName(name)
    localStorage.setItem('dad', name);
  }
}, []);

(3) Hooks cannot be used in class components.

Hooks must be initialized in either a functional component or in a custom hook function. Custom hook functions can only be called within a functional component and must follow the same rules as non-custom hooks.

You can still use class components within the same application. You can render your functional component with hooks as a child of a class component.

(4) Custom hooks should start with the word use and be camel-cased.

This is more of a strong suggestion than a rule, but it will help with consistency in your application. You'll also know that when you see a function prefixed with the word use that it's probably a custom hook.

What is a custom hook?

Custom hooks are just functions that follow the same rules as non-custom hooks. They allow you to consolidate logic, share data, and re-use hooks across components.

When should I use custom hooks?

Custom hooks are best used when you need to share logic between components. In JavaScript, when you want to share logic between two separate functions, you create another function to support that. Well, like components, hooks are functions. You can extract hook logic to be shared between components all around your application. When writing custom hooks, you name them (again, starting with the word use), set the parameters, and tell them what (if anything) they should return.

For Example:

import { useEffect, useState } from 'react';

const useFetch = ({ url, defaultData = null }) => {
  const [data, setData] = useState(defaultData);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then((res) => {
        setData(res);
        setLoading(false);
      })
      .catch((err) => {
        setError(err);
        setLoading(false);
      });
  }, []);

  const fetchResults = {
    data,
    loading,
    error,
  };

  return fetchResults;
};

export default useFetch;

Are you trying to come up with a situation that calls for a custom hook? Use your imagination. Although there are unconventional rules alongside hooks, they are still very flexible and have only begun to display their potential.

What are the benefits of using custom hooks?

Hooks allow you to curb complexity as your app grows and write more approachable code that is much easier to digest. The code below is a comparison of two components that have the same functionality. After the first comparison, we will demonstrate more benefits using a custom hook in a component that is accompanied by a container.

The following class component should look familiar:

import React from 'react';

class OneChanceButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      clicked: false,
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    return this.setState({ clicked: true });
  }

  render() {
    return (
      <div>
        <button
          onClick={this.handleClick}
          disabled={this.state.clicked}
        >
          You Have One Chance to Click
        </button>
      </div>
    );
  }
}

export default OneChanceButton;

How about that same functionality implemented with hooks to simplify the code and increase readability:

import React, { useState } from 'react';

function OneChanceButton(props) {
  const [clicked, setClicked] = useState(false);

  function doClick() {
    return setClicked(true);
  }

  return (
    <div>
      <button
        onClick={clicked ? undefined : doClick}
        disabled={clicked}
      >
        You Have One Chance to Click
      </button>
    </div>
  );
}

export default OneChanceButton;

More complex comparisons

Looking for more?

  1. media-query-custom-hooks - this comparison will demonstrate the power of implementing custom hooks by utilizing useState and the useEffect methods
  2. media-query-custom-hooks - an even more complex example using useRef and useReducer

Conclusion

React hooks are an amazing new feature! The reasons for the implementation are justified; and, coupled with that, I believe that this will lower the barrier to coding in React tremendously and keep it on the top of the favorite frameworks list. It will be very exciting to see how this changes the way third party libraries work, especially state management tools and routers.

In summary, React hooks:

  1. Make it easy to "hook" into React's lifecycle methods without using a class component
  2. Help to reduce code by increasing re-usability and abstracting complexity
  3. Help ease the way data is shared between components

I can't wait to see more powerful examples of how React hooks can be utilized. Thanks for reading!

Ready for more? Check out the next part: React Hooks - A deeper dive featuring useContext and useReducer.

Austin Johnston

Austin Johnston

Here is Austin, a full-stack web developer based in Denver, CO. He believes the tech industry is a confluence of the public and its problems. This merge inspires him to get involved with the community and gain understanding of the unique challenges associated with it. Outside of developing, Austin takes a liking to enduro mountain biking and free-ride snowboarding.

Share this tutorial

Featured Course

Authentication with Flask, React, and Docker

This course details how to add user authentication to a Flask and React microservice. You'll use React Testing Library and pytest to test both apps, Formik to manage form state, and GitLab CI to deploy Docker images to Heroku.

Featured Course

Authentication with Flask, React, and Docker

This course details how to add user authentication to a Flask and React microservice. You'll use React Testing Library and pytest to test both apps, Formik to manage form state, and GitLab CI to deploy Docker images to Heroku.