React: Design Patterns | Custom Hooks in React

Andres Zenteno
The Tech Pulse
Published in
4 min readJun 23, 2024

--

Photo by Lautaro Andreani on Unsplash

React is a powerful library for building user interfaces, but as your application grows, you might find yourself repeating the same logic across multiple components. This is where custom hooks come in. Custom hooks allow you to encapsulate and reuse logic, making your code cleaner and more maintainable.

What Are Custom Hooks?

Custom hooks are essentially functions that let you extract component logic into reusable pieces. Instead of using React’s built-in hooks like useState and useEffect directly in your components, you can combine them into your own custom hooks. For example, if you need to fetch dog breed information from a server, you could either handle the state and fetching logic inside each component or create a custom hook like useDogBreeds to encapsulate this functionality.

Here’s a simple example: imagine you want to load dog breeds from a server. You could create a custom hook called useDogBreeds that handles the fetching and state management. Then, in your component, you simply call this hook to get the dog breeds. This not only makes your component code cleaner but also allows you to reuse the same logic in multiple components.

Creating a Custom Hook: 'useCurrentBreed'

Let’s start with a basic example: loading the current dog breed from a server. First, create a new file for your custom hook, useCurrentBreed.js. Custom hooks must start with the word “use” because React relies on this naming convention to identify them as hooks.

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

export const useCurrentBreed = () => {
const [breed, setBreed] = useState(null);

useEffect(() => {
(async () => {
const response = await axios.get('/current-breed');
setBreed(response.data);
})();
}, []);

return breed;
};

In this example, useCurrentBreed is a function that uses useState to manage the breed state and useEffect to fetch the breed data when the component mounts. The hook returns the current breed, which can be used in any component.

Using the Custom Hook

To use this custom hook in a component, simply import it and call it like any other hook:

import React from 'react';
import { useCurrentBreed } from './useCurrentBreed';

const BreedInfo = () => {
const breed = useCurrentBreed();

if (!breed) return <div>Loading...</div>;

return (
<div>
<h1>{breed.name}</h1>
<p>{breed.description}</p>
</div>
);
};

export default BreedInfo;

Here, BreedInfo uses the useCurrentBreed hook to get the current breed and display its information. This makes the component code much simpler and easier to read.

Extending the Hook: 'useBreed'

What if you want to load any breed by its ID? You can extend the useCurrentBreed hook to create a more flexible useBreed hook. Start by creating a new file, `useBreed.js`, and modify the existing hook:

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

export const useBreed = (breedId) => {
const [breed, setBreed] = useState(null);

useEffect(() => {
(async () => {
const response = await axios.get(`/breeds/${breedId}`);
setBreed(response.data);
})();
}, [breedId]);

return breed;
};

This hook takes a breedId as an argument and fetches the corresponding breed data. The useEffect dependency array includes breedId, so the effect runs whenever the breedId changes.

Generalizing Further: 'useResource'

To make your hooks even more reusable, you can create a generic useResource hook that can load any resource from a given URL. Create a new file, useResource.js, and define the hook:

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

export const useResource = (resourceUrl) => {
const [resource, setResource] = useState(null);

useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setResource(response.data);
})();
}, [resourceUrl]);

return resource;
};

This hook takes a URL as an argument and fetches the resource from that URL. You can now use this hook to load any resource, not just dog breeds.

Ultimate Flexibility: 'useDataSource'

To achieve maximum flexibility, you can create a useDataSource hook that accepts a function specifying how to get the data. This allows you to load data from various sources, not just URLs. Create a new file, useDataSource.js, and define the hook:

import { useState, useEffect } from 'react';

export const useDataSource = (getResourceFunction) => {
const [resource, setResource] = useState(null);

useEffect(() => {
(async () => {
const result = await getResourceFunction();
setResource(result);
})();
}, [getResourceFunction]);

return resource;
};

This hook takes a function as an argument. The function specifies how to get the data, making the hook extremely flexible.

Using 'useDataSource'

Here’s how you might use `useDataSource` in a component:

Fetching from an API

import React from 'react';
import axios from 'axios';
import { useDataSource } from './useDataSource';

const BreedInfo = ({ breedId }) => {
const getBreed = async () => {
const response = await axios.get(`/breeds/${breedId}`);
return response.data;
};

const breed = useDataSource(getBreed);

if (!breed) return <div>Loading...</div>;

return (
<div>
<h1>{breed.name}</h1>
<p>{breed.description}</p>
</div>
);
};

export default BreedInfo;

In this example, getBreed is a function that fetches breed data from the server. The `useDataSource` hook uses this function to get the data.

Fetching from Local Storage

import React from 'react';
import { useDataSource } from './useDataSource';

const BreedInfoFromLocalStorage = ({ breedId }) => {
const getBreedFromLocalStorage = () => {
const breed = localStorage.getItem(`breed-${breedId}`);
return breed ? JSON.parse(breed) : null;
};

const breed = useDataSource(getBreedFromLocalStorage);

if (!breed) return <div>Loading...</div>;

return (
<div>
<h1>{breed.name}</h1>
<p>{breed.description}</p>
</div>
);
};

export default BreedInfoFromLocalStorage;

In this example, getBreedFromLocalStorage is a function that retrieves breed data from local storage. The useDataSource hook uses this function to get the data.

Custom hooks are a powerful feature in React that allow you to encapsulate and reuse logic across your application. By creating custom hooks like `useCurrentBreed`, `useBreed`, `useResource`, and `useDataSource`, you can make your code cleaner and more maintainable. The key is to identify common patterns in your components and extract them into reusable hooks. This not only reduces duplication but also makes your components easier to understand and test.

React: Design Patterns is a course by Shaun Wassell that you can follow on LinkedIn Learning.

--

--