Austin Roy Omondi Live long and prosper 👌

How to create your own React hooks

6 min read 1708

Hooks overview

A while back the React team unveiled hooks, much to the excitement of the developer community. But what’s all the fuss about? Well, hooks unlock a whole new way to write functional components by allowing us to add features available to class components such as stateful logic.

React primarily lets you do this using the State and Effect hooks. The State(useState) hook allows you to define a state object and a function that updates it. The Effect(useEffect) hook allows you to perform side effects in a functional component, think of it like lifecycle events in class components.

A custom hook is a function that starts with the word “use” and may call other hooks. The “useWhatever” naming convention is mainly to allow the linter to find bugs in the use of these hooks — in scenarios where usage goes against the rules of hooks.

Rules of hooks

The general rules of hooks also apply to custom hooks. These include:

  • Only call hooks at the top level. Don’t call hooks inside loops, conditions, or nested functions.
  • Only call hooks from React function components. Don’t call hooks from regular JavaScript functions. (There is just one other valid place to call hooks — your own custom hooks. We’ll learn about them in a moment.)

In case you are wondering why these rules are in place, it’s because React relies on the order in which hooks are called to associate the hooks with a certain local state. Placing a hook inside conditions may change this order resulting in subsequent hooks failing to get called which will, more likely than not, result in bugs.

 

This is illustrated on the React docs using a form with several hooks as shown below:

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');
  // 2. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });
  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');
  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });
  // ...
}

These hooks are called in the following order on two renders:

// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title
// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title
// ...

If we are to call the second hook inside a condition so that it only saves when data is entered as shown below, this would go against the rules of hooks:

if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

The result is the third and fourth hooks fail to read state and apply the desired effects respectively. Fortunately, this can be fixed by moving the condition inside of the hook:

useEffect(function persistForm() {
    // 👍 We're not breaking the first rule anymore
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

More on this can be found on the rules of hooks section of the React docs.

Creating our app

Let’s look at how we can create our own hooks, to do this we’ll build a small application that makes use of a custom React hook that we’ll add to it. Our app will be a basic cryptocurrency checker that will allow us to check the value in U.S. dollars of some popular cryptocurrencies. For this demo, we’ll only check Ethereum and Bitcoin but the same steps can be followed to add other coins.

To get this up and running, we’ll be using create-react-app to generate boilerplate code for our application and the dropdown component from semantic-ui-react.

Let’s get started, run the following code in your console to bootstrap your app:

create-react-app hooked-cryptochecker

The next step would be to install our two dependencies, semantic-ui-react and dotenv. In your terminal once inside the project directory, run the following command to do so:

yarn add semantic-ui-react dotenv

We’ll be making use of the API from coinapi.io to get the current values of Etherium and Bitcoin. To do so we’ll need to get an API key from them, fortunately, they provide these for free. Head over to CoinAPI to get yours. Once you have your API key, create a .env file in the root directory of your project and paste your API key there.

Inside either App.js or Index.js, paste the following code to load environment variables.

Creating custom hooks

Now that we are all set up, let’s get to the meat of the application. Create a file called CryptoChecker.jsx in the components directory and place the following code in it:

import React, { useState, useEffect } from 'react'
import { Dropdown } from 'semantic-ui-react'

const coinAPIKey = process.env.REACT_APP_COIN_API_KEY

const CryptoChecker = () => {

  const [coinName, setCoinName] = useState(null)
  const coinUrl = `https://rest.coinapi.io/v1/exchangerate/${coinName}/USD`

  const useCryptoFetcher = () => {
    const [coinData, setCoinData] = useState(null)
    const [fetched, setFetched] = useState(false)
    const [loading, setLoading] = useState(false)

    useEffect(() => {
      setLoading(true)
      fetch(coinUrl,{
        headers: {
          "X-CoinAPI-Key": coinAPIKey
        }
      }).then(res => {
        if(!coinUrl){
          setFetched(false)
          return null
        }
        if(!res.ok){
          setFetched(false)
          return null
        }
        else {
          return res.json()
        }
      }).then( data => {
        setLoading(false)
        setFetched(true)
        setCoinData(data)
      }
      )
    }, [coinUrl])
   return ([coinData, loading, fetched])
  }

  const mapCoinData = () => {
    if(!fetched) return <div>No data fetched</div>
    if(loading) return <div>Loading...</div>
    if(!coinData){
      return <div>No Coin Data</div>
    } else {
      return (
        <div>
          <h1>{coinName}</h1>
          <div>{coinData.rate} USD</div>
        </div>
      )
    }
  }

  const [ coinData, loading, fetched ]  = useCryptoFetcher();
  const coinOptions = [
    {
      key: 'BTC',
      value: 'BTC',
      text: 'Bitcoin'
    },
    {
      key: 'ETH',
      value: 'ETH',
      text: 'Ethereum'
    }
  ]

  return(
    <div>
        <Dropdown
        placeholder='Select Coin'
        clearable
        selection
        options={coinOptions}
        onChange={ (e, {value}) => setCoinName(value)}
      />
      <br/>
      {mapCoinData()}
    </div>
  )
}

export default CryptoChecker;

Let’s go through our component to see how it works. CryptoChecker is our functional component which returns a dropdown that allows us to choose which coin we wish to check, underneath it, we’ll display the name of the coin accompanied by its value in U.S dollars.

We’ve used the state hook to initiate the name of the coin we wish to search and placed it in state. We then use it to set the URL that we’ll be hitting to get our coin data.

The next thing you will notice is a function called useCryptofetcher, this is our custom hook. It returns the coin data as well as our API call state(loading or completed) as well as a boolean called fetched that tells us when we have fetchedany data.

Our custom hook makes use of both the effect and state hooks. We use the state hook to place our coin data in state as well as update the state of our API call to know when data is being loaded and when calls are complete. The effect hook is used to trigger a call to coinAPI.io fetching the exchange rate value of our coin. We optimize the effect hook by passing it a second argument, an array containing the URL, this ensures side effects are only applied when the URL changes, therefore avoiding unnecessary re-renders as well as repeated API calls.

We then have a function called mapCoinDatathat makes use of the data returned by our custom hook, changing what is displayed in the DOM depending on what values are returned. To make these values available to mapCoinData, we’ll restructure it from useCryptoFetcher, placing it in the general scope of our component.

We have an array called coinOptionswhich contains the names of the coins we’ll have in our dropdown, this is where you can provide more options if you wish to fetch the values of other coins.

Great, our component is ready to use, complete with a personalized hook to add some functionality to it. Let’s go ahead and make use of our awesome new component, edit App.js to add it to our app. It should look something like this:

import React, { Component } from 'react';
import './App.css';
import CryptoChecker from './components/CryptoChecker';
require('dotenv').config()

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Hooked CryptoChecker</h1>
        <CryptoChecker />
      </div>
    );
  }
}

export default App;

Now it’s time to fire up our application and see the magic. In your terminal run the yarn start command and try out the application.

The app on initial loading
Dropdown Options
App with Bitcoin selected

Conclusion

Custom hooks really open up new ways to write components, allowing you to tailor the functionality to your liking. Overall, hooks have added a lot of flexibility to how we can write React apps by minimizing the need for class-based components. Hooks also allow us to write more optimized components by eliminating the complexities of class-based components, this is because functional components are pure components, free of unnecessary logic.

You can expand on the functionality of these hooks using some additional hooks that come built-in with react to create even more amazing hooks of your own.

Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.

Austin Roy Omondi Live long and prosper 👌

Leave a Reply