How to Implement Custom Hooks in React
This article provides a practical guide on how to create and implement custom hooks in React. You will learn the core concepts behind custom hooks, understand when to implement them to share stateful logic between components, and walk through a step-by-step example of building and consuming your own custom hook.
What is a Custom Hook?
A custom hook is a JavaScript function whose name starts with “use”
and that can call other hooks. In React, custom hooks allow you to
extract component logic into reusable functions. Instead of copying and
pasting the same useState or useEffect logic
across multiple components, you bundle that logic into a single custom
hook.
Rules of Custom Hooks
When implementing custom hooks, you must follow two strict React
rules: 1. Name must start with “use”: React uses this
naming convention (e.g., useAuth, useFetch) to
automatically check for violations of the rules of hooks. 2.
Call hooks at the top level: Do not call custom hooks
inside loops, conditions, or nested functions.
Step-by-Step Implementation
To implement a custom hook, follow these three steps: 1.
Identify Reusable Logic: Find stateful logic (using
useState, useEffect, etc.) that is repeated in
your components. 2. Extract to a Separate Function:
Move this logic into a new function that starts with use.
3. Return State or Functions: Return the values,
states, or updater functions that your UI components need.
Practical Example:
Creating a useFetch Hook
Below is a step-by-step implementation of a custom hook that fetches data from an API and manages loading and error states.
Step 1: Define the Custom Hook
Create a new file named useFetch.js. This hook will
manage data, loading, and error
states, and execute the API call inside a useEffect
block.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;Step 2: Use the Custom Hook in a Component
Now, you can import and use the useFetch hook in any
component. This keeps your component UI clean and separated from the
data-fetching logic.
import React from 'react';
import useFetch from './useFetch';
function UserList() {
const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;Key Takeaways
- Separation of Concerns: Custom hooks decouple your UI rendering logic from your business and stateful logic.
- Independent State: Every time you use a custom hook, all state and effects inside of it are completely isolated to that specific component instance.
- Clean Codebases: Consolidating repetitive code into custom hooks reduces bug surface area and makes unit testing much easier.