React’s built-in hooks like useState
and useEffect
are powerful, but sometimes, you need reusable logic that can be shared across multiple components. That is where custom hooks come in.
In this guide, we will walk through creating a custom hook step by step, focusing on fetching data from an API using useState
and useEffect
.
What is a Custom Hook?
A custom hook in React is simply a function that starts with use
and allows you to reuse stateful logic across components.
Why use custom hooks?
Improves code reusability
Reduces duplication
Keeps components clean and readable
Step 1: Setting Up the Project
To follow along, ensure you have a React project ready. If not, create one using:
npx create-react-app custom-hook-example
cd custom-hook-example
npm start
Step 2: Creating a Basic API Fetch Hook (useFetch
)
Let’s create a custom hook that fetches data from an API.
Inside your src
folder, create a new file: useFetch.js
The Custom Hook
import { useState, useEffect } from "react";
const 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("Failed to fetch data");
}
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 3: Using useFetch
in a Component
Now, let’s use our custom hook inside a React component.
Open App.js
and update it:
Using useFetch
in App.js
import React from "react";
import useFetch from "./useFetch";
const App = () => {
const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/posts");
if (loading) return <h2>Loading...</h2>;
if (error) return <h2>Error: {error}</h2>;
return (
<div>
<h1>Posts</h1>
<ul>
{data.slice(0, 5).map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default App;
Step 4: Explanation of the Hook
State Management
We use useState
to manage data
, loading
, and error
states.
Side Effects
We use useEffect
to fetch data when the component mounts or when the url
changes.
Reusability
This hook can be used in multiple components without rewriting the logic, making the code more modular and easier to maintain.
Step 5: Extending the Custom Hook
To make our hook more flexible, we can modify useFetch
to accept an options
parameter for fetch
.
const useFetch = (url, options = {}) => {
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error("Failed to fetch data");
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, options]);
};
Now, useFetch
can send POST
, PUT
, or DELETE
requests by passing different options.
Common Use Cases for Custom Hooks
Fetching Data - The
useFetch
hook we built can be reused across multiple components to fetch data.Form Handling - Custom hooks can help manage form state and validation logic.
Debouncing Input - Creating a
useDebounce
hook helps optimize performance when handling user input.Theme Management - A
useTheme
hook can be used to toggle between dark and light mode.Local Storage Handling - A
useLocalStorage
hook can be created to store and retrieve data from the browser's local storage.
Conclusion
Custom hooks help in creating more modular and maintainable React applications. By following this guide, you have learned how to:
Create a custom hook for fetching data.
Use it inside a React component.
Extend it to make it more flexible.
By implementing custom hooks, you can simplify your code, improve performance, and enhance reusability. Experiment with creating your own hooks for different functionalities in your applications.