Custom Hooks in React
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.
Conclusion
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.
#coding/react/designpatterns #reading/medium/thetechpulse