React Custom Hooks vs. Helper Functions - When To Use Both
Tags: webdev, javascript, programming, react
Published: 2024-07-03 14:16:42

When working as a developer it is fairly common to come across various technologies and use cases on a day-to-day basis. Two popular concepts are React Custom Hooks and Helper functions. The concept of Helper functions has been around for a very long time whereas React Custom Hooks are still fairly modern. Both concepts allow developers to abstract and reuse the code that they write in different ways although they both have slightly different use cases.

Today we will take a look at the similarities between the two and conclude on when is the right time to use each of them.

Let's start by taking a look at what React Custom Hooks are.

## What are React Custom Hooks?

React Custom Hooks are JavaScript functions which give you the ability to reuse stateful logic throughout your React codebase. When using Custom Hooks we tap into the React Hooks API that lets us manage our state and its side effects in a way that follows the React functional component process.

One of the unique characteristics of Custom Hooks is being able to leverage state management which means that we can access the React built-in hooks like `useState`, and `useEffect`. Another unique identifier is the fact that we have to follow the named conventions for React Custom Hooks as we must prefix the start of the hook with the word `use` followed by its name, for example, `useFetch`.

When we use Custom Hooks they can access and change a component state, plus lifecycle methods as they are deeply interconnected with the logic for React components.

We can see an example of what a React Custom Hook looks like in this code example:

```javascript
import { useState, useEffect } from 'react';

export function useFetch(url) {
  const [data, setData] = useState([]);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const json = await fetch(url).then((r) => r.json());
        setIsLoading(false);
        setData(json);
      } catch (error) {
        setError(error);
        setIsLoading(false);
      }
    };

    fetchData();

  return { data, error, isLoading };
}
```

This Custom Hook is called `useFetch` and has reusable logic for fetching data from an API. It can manage the loading and error states and can be imported into multiple components.

Now that we have a fundamental understanding of React Custom Hooks let's see how they compare to Helper Functions.

## What are Helper Functions?

Helper functions are essentially standalone functions which are used for doing different calculations or tasks. These types of functions can be used anywhere inside your applications as they are not part of the React component or state management system. Another key difference is that they can be used in numerous programming languages and are not tied to any ecosystem. They are a concept that can be used anywhere.

Unlike React Custom Functions, Helper functions perform calculations and tasks which are relevant to the given input. They cannot interact with side effects or component states. They also do not need predefined naming conventions like `use` and should be named based on whatever task you have designated them for.

Take a look at this helper function in this example here:

```javascript
import dayjs from 'dayjs';

function formatDate(date) {
  return dayjs(date).format('MM/DD/YYYY');
}

export default formatDate;
```

In this code snippet, we use the Javascript date library [Day.js](https://day.js.org/) to parse and format the date, which gives us a more powerful method for formatting our dates.

Ok with our updated understanding of React Custom Hooks and helper functions it's now a great time to see how we can incorporate both of them into a simple application. In the next section, we are going to build an app that uses them both.

## Building an app that uses a Custom Hook and Helper Function

The app we will be building is a simple Pokémon Pokédex application which you can see in this picture.

![Pokemon Pokedex App Screen](https://res.cloudinary.com/d74fh3kw/image/upload/v1719917912/pokemon-app_hzkkag.png)

We get the Pokemon data and information from the [Pokémon API](https://pokeapi.co/) and then we use the data to build our application which is then styled using Tailwind CSS. With our explanation done it's time to start building our app.

You can find the codebase online here on GitHub [https://github.com/andrewbaisden/pokemon-pokedex-app](https://github.com/andrewbaisden/pokemon-pokedex-app)

The first thing we have to do is set up our project structure so find a location on your computer for the project like the desktop and then run these commands to create a Next.js project.

> On the Next.js setup screen make sure that you select yes for Tailwind CSS and the App Router so that our projects have the same setup. In this project, we will be using JavaScript, the other default settings should be fine.

```shell
npx create-next-app pokemon-pokedex-app
cd pokemon-pokedex-app
```

We should now have a Next.js project and we should be inside of the `pokemon-pokedex-app` folder so the next step is to install the JavaScript packages which we need for this application. We have to install `dayjs` for calculating times and dates and `uuid` for creating unique IDs for our Pokémon.

Install both packages with this command:

```shell
npm i dayjs uuid
```

Now that our packages are installed next we are going to create all of the files and folders for our project.

Run the command below to create our project architecture:

```shell
cd src/app
mkdir components hooks utils
mkdir components/Header components/Pokemon components/PokemonDetails
touch components/Header/Header.js components/Pokemon/Pokemon.js components/PokemonDetails/PokemonDetails.js
touch hooks/usePokemon.js
touch utils/dateUtils.js utils/fetchUtils.js
cd ../..
```

With this command we:

- Create a components folder for our Header, Pokemon and PokemonDetails components
- Create a hooks folder for our `usePokemon` hook which fetches data from the Pokemon API
- Create a `utils` folder for our fetch and date utility functions

Ok, in the next steps, we shall add the code to our files and then our project will be completed, so open the project in your code editor.

Up first is our `next.config.mjs` file in our root folder. 

Replace all of the code in that file with this new code here:

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'raw.githubusercontent.com',
      },
    ],
  },
};

export default nextConfig;
```

All we are doing in this file is adding an image pattern for GitHub so that we can access the Pokémon images in our application without getting an error. We have to define that route so that it is approved.

Now let's do our `layout.js` file so replace all of the code with the code below:

```javascript
import { Yanone_Kaffeesatz } from 'next/font/google';
import './globals.css';

const yanone = Yanone_Kaffeesatz({ subsets: ['latin'] });

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={yanone.className}>{children}</body>
    </html>
  );
}
```

The main change in this file is using the `Yanone_Kaffeesatz` Google font for our application which replaces the default `Inter` font.

The `globals.css` file is next on our list we just have to do some code cleanup. 

Like before replace the code with this snippet:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  font-size: 20px;
}
```

We cleaned up some of the code and made the default font size 20px for our application.

That takes care of our initial configuration files we just need to add the code for our components, hooks, utils and main page and our application will be ready.

Starting from the top let's do our `Header.js` component inside of `Header/Header.js`. 

Add this code to our file:

```javascript
import { useState, useEffect } from 'react';
import { getLiveDateTime } from '../../utils/dateUtils';

export default function Header() {
  const [dateTime, setDateTime] = useState(getLiveDateTime());

  useEffect(() => {
    const interval = setInterval(() => {
      setDateTime(getLiveDateTime());
    }, 1000);

    return () => clearInterval(interval);
  }, []);
  return (
    <>
      <header className="flex row justify-between items-center bg-slate-900 text-white p-4 rounded-lg">
        <div>
          <h1 className="text-5xl uppercase">Pokémon</h1>
        </div>
        <div>
          <p>Date: {dateTime.date}</p>
          <p>Time: {dateTime.time}</p>
        </div>
      </header>
    </>
  );
}
```

This component essentially displays the title for our application which is Pokémon and it also shows the live date and time. This is accomplished by importing the `utils/dateUtils.js` helper function that uses the `dayjs` JavaScript library for calculating the time and date.

The next file to work on will be the `Pokemon.js` file in the `Pokemon` folder. 

Here is the code for our file:

```javascript
import { useState, useEffect } from 'react';
import usePokemon from '../../hooks/usePokemon';
import { fetchPokemon } from '../../utils/fetchUtils';
import PokemonDetails from '../PokemonDetails/PokemonDetails';

export default function Pokemon() {
  const { data, isLoading, error } = usePokemon(
    'https://pokeapi.co/api/v2/pokemon'
  );

  const [pokemonDetails, setPokemonDetails] = useState([]);

  useEffect(() => {
    const fetchPokemonDetails = async () => {
      if (data && data.results) {
        const details = await Promise.all(
          data.results.map(async (pokemon) => {
            const pokemonData = await fetchPokemon(pokemon.url);
            return pokemonData;
          })
        );
        setPokemonDetails(details);
      }
    };

    fetchPokemonDetails();
  }, [data]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <>
      <div className="flex row flex-wrap gap-4 justify-evenly">
        <PokemonDetails pokemonDetails={pokemonDetails} />
      </div>
    </>
  );
}
```

This our our main Pokémon component file which uses our `usePokemon.js` hook for fetching data from the Pokémon API. This works alongside our utility `fetchUtils.js` file for fetching data. We have an error handling setup for fetching data and our state data is passed down into our `PokemonDetails.js` component that renders our user interface.

Right, we should add the code for our `PokemonDetails.js` file now in the `PokemonDetails` folder. 

Put this code in the file:

```javascript
import Image from 'next/image';
import { v4 as uuidv4 } from 'uuid';

export default function PokemonDetails({ pokemonDetails }) {
  return (
    <>
      {pokemonDetails.map((pokemon) => (
        <div
          key={pokemon.id}
          className={
            pokemon.types[0].type.name === 'fire'
              ? 'bg-orange-400'
              : pokemon.types[0].type.name === 'water'
              ? 'bg-blue-400'
              : pokemon.types[0].type.name === 'grass'
              ? 'bg-green-400'
              : pokemon.types[0].type.name === 'bug'
              ? 'bg-green-700'
              : pokemon.types[0].type.name === 'normal'
              ? 'bg-slate-400'
              : ''
          }
        >
          <div className="text-white p-4">
            <div className="capitalize">
              <h1 className="text-4xl">{pokemon.name}</h1>
            </div>
            <div className="flex row gap-2 mt-4 mb-4">
              <div className="bg-indigo-500 shadow-lg shadow-indigo-500/50 p-2 rounded-lg text-sm">
                Height: {pokemon.height}
              </div>
              <div className="bg-indigo-500 shadow-lg shadow-indigo-500/50 p-2 rounded-lg text-sm">
                Weight: {pokemon.weight}
              </div>
            </div>
            <div className="bg-white text-black rounded-lg p-4">
              {pokemon.stats.map((stat) => (
                <div key={uuidv4()}>
                  <div className="capitalize flex row items-center gap-2">
                    <table>
                      <tr>
                        <td width={110}>{stat.stat.name}</td>
                        <td width={40}>{stat.base_stat}</td>
                        <td width={40}>
                          <div
                            style={{
                              width: `${stat.base_stat}px`,
                              height: '0.5rem',
                              backgroundColor: `${
                                stat.base_stat <= 29
                                  ? 'red'
                                  : stat.base_stat <= 60
                                  ? 'yellow'
                                  : 'green'
                              }`,
                            }}
                          ></div>
                        </td>
                      </tr>
                    </table>
                  </div>
                </div>
              ))}
            </div>
            <div>
              <Image
                priority
                alt={pokemon.name}
                height={300}
                width={300}
                src={pokemon.sprites.other.home.front_default}
              />
            </div>
          </div>
        </div>
      ))}
    </>
  );
}
```

Pretty much all of the code in this file is used for creating the interface for our Pokémon application. The styling is done using Tailwind CSS.

Just a few more files to do before the project is done. The next file to work on will be the `usePokemon.js` file in our `hooks` folder. 

Our file will need this code so add it now:

```javascript
import { useState, useEffect } from 'react';
import { fetchPokemon } from '../utils/fetchUtils';

const usePokemon = (initialUrl) => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const pokemonData = await fetchPokemon(initialUrl);
        setData(pokemonData);
      } catch (error) {
        setError(error);
      } finally {
        setIsLoading(false);
      }
    };
    fetchData();
  }, [initialUrl]);
  return { data, isLoading, error };
};

export default usePokemon;
```

This custom hook is used for fetching data from an API and in our case it will be for the Pokémon API.

Now we will complete our `dateUtils.js` file in the `utils` folder with this code:

```javascript
import dayjs from 'dayjs';

export const getLiveDateTime = () => {
  const now = dayjs();
  return {
    date: now.format('MMMM D, YYYY'),
    time: now.format('h:mm:ss A'),
  };
};
```

With this utility file, we use the `dayjs` JavaScript library to calculate dates and times in any file it is imported into.

Ok now for our second utility file, `fetchUtils.js` add this code to the file:

```javascript
export const fetchPokemon = async (url) => {
  try {
    const response = await fetch(url);
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Error fetching Pokemon:', error);
    throw error;
  }
};
```

This utility file works with our `usePokemon.js` hook to fetch data from an API.

Finally, let's complete our project by replacing and adding the code to our main `page.js` file in the root folder. 

This is the code we need for this file:

```javascript
'use client';
import Header from './components/Header/Header';
import Pokemon from './components/Pokemon/Pokemon';

export default function PokemonList() {
  return (
    <div className="p-5">
      <Header />
      <h1 className="text-4xl mt-4 mb-4">Pokédex</h1>
      <Pokemon />
    </div>
  );
}
```

Our `page.js` file is the main entry point for all of our components and with this code, our project is now completed.

Run your project using the usual Next.js run script as shown here and you should see the Pokémon Pokédex application in your browser:

```shell
npm run dev
```

## Conclusion

Today we learned how it's important to know the differences between helper functions and React Custom if you want to develop code that is organised, clean, and manageable. With stateful logic reuse in React, custom hooks are recommended because helper functions work best for stateless, general-purpose jobs. It is possible to enhance your codebase's modularity and reusability by making the best judgement on when to use both.