Austin Roy Omondi Live long and prosper 👌

How to create your own React Hooks

6 min read 1715

create custom hooks

Editor’s note: This post was updated 1 December 2021.

What is React Hooks?

A while back, the React team unveiled Hooks, much to the excitement of the developer community. What’s all the fuss about? Well, Hooks unlocks 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, such as scenarios where usage goes against the rules of Hooks.

Rules of React Hooks

The general rules of Hooks also apply to custom Hooks. These are:

  • 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 in the rules of Hooks section of the React docs.

Creating our React app and custom Hooks

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. 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 CRA 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:

npx create-react-app hooked-cryptochecker

The next step is to install our two dependencies, semantic-ui-react and dotenv. In your terminal, run the following command to do so:

yarn add semantic-ui-react semantic-ui css dotenv

Add .env to your .gitigignore file, as the default gitignore generated by CRA does not include it. This is where we will be placing our environment variables.

We’ll be making use of the API from 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, so 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.

More great articles from LogRocket:

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


Import semantic-ui’s stylesheet into the index.js file:

import 'semantic-ui-css/semantic.min.css'

Creating custom Hooks

Now that we are set up, let’s get to the meat of the application. Create a components directory under the src directory by running:

mkdir src/components

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 useCryptoFetcher = () => {
   const [coinData, setCoinData] = useState(null);
   const [fetched, setFetched] = useState(false);
   const [loading, setLoading] = useState(false);
   useEffect(() => {
     const coinUrl = `${coinName}/USD`;
     if (coinName) {
       fetch(coinUrl, {
         headers: {
           "X-CoinAPI-Key": coinAPIKey,
         .then((res) => {
           if (!coinUrl) {
             return null;
           if (!res.ok) {
             return null;
           } else {
             return res.json();
         .then((data) => {
   }, [coinName]);
   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>{coinData.rate} USD</div>
 const [coinData, loading, fetched] = useCryptoFetcher();
 const coinOptions = [
     key: "BTC",
     value: "BTC",
     text: "Bitcoin",
     key: "ETH",
     value: "ETH",
     text: "Ethereum",
 return (
       placeholder="Select Coin"
       onChange={(e, { value }) => setCoinName(value)}
     <br />
export default CryptoChecker;

Let’s go through our component to see how it works. CryptoChecker is our functional component that 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 placing 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, which 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 fetched any 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 to fetch the exchange rate value of our coin.

We optimize the effect Hook by passing it a second argument, an array containing the URL, which ensures side effects are only applied when the URL changes. This avoids unnecessary re-renders as well as repeated API calls.

We also check the coinName variable to ensure we only make the API call when it is not null. This optimizes the component by preventing an API call when the page loads for the first time.

We then have a function called mapCoinData that 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 destructure it from useCryptoFetcher, placing it in the general scope of our component.

We have an array called coinOptions, which 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 and edit App.js to add it to our app. It should look something like this once:

import React, { Component } from 'react';
import './App.css';
import CryptoChecker from './components/CryptoChecker';
class App extends Component {
 render() {
   return (
     <div className="App">
       <h1>Hooked CryptoChecker</h1>
       <CryptoChecker />
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.

react hooks cryptochecker no coin selected


select cryptochecker coincryptochecker bitcoin selected


At the very core, fetching data with Hooks boils down to the same things as most other data fetching methods used in React:

  • Tracking the variables needed to make the API call, from data to be sent to triggers that run the calls
  • Making the API call with your preferred method, i.e., fetch, Axios, etc
  • Tracking the loading and error states
  • Tracking the response returned
  • Displaying the relevant data


Custom Hooks really opens 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. 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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Austin Roy Omondi Live long and prosper 👌

Leave a Reply