A custom hook is a reusable function in React that starts with the word use and lets you encapsulate logic involving state, effects, or other hooks. It helps in keeping components clean and promotes code reuse.
✅ Why Use Custom Hooks?
- Share logic across multiple components
- Reduce code duplication
- Improve readability and maintainability
- Keep component code clean and focused on UI
🛠️ How to Create a Custom Hook
Example: useCounter
// useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
export default useCounter;
Usage:
import useCounter from './useCounter';
function CounterComponent() {
const { count, increment, decrement, reset } = useCounter(10);
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>➕</button>
<button onClick={decrement}>➖</button>
<button onClick={reset}>🔄</button>
</div>
);
}
🌐 Real-world Custom Hook: useFetch
Handles API data fetching with loading and error states.
// useFetch.js
import { useEffect, useState } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
async function fetchData() {
try {
setLoading(true);
const res = await fetch(url);
if (!res.ok) throw new Error("Failed to fetch");
const result = await res.json();
if (isMounted) setData(result);
} catch (err) {
if (isMounted) setError(err.message);
} finally {
if (isMounted) setLoading(false);
}
}
fetchData();
return () => {
isMounted = false; // Cleanup
};
}, [url]);
return { data, loading, error };
}
export default useFetch;
Usage:
import useFetch from './useFetch';
function UserList() {
const { data, loading, error } = useFetch('https://api.example.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
📌 Best Practices
- Name should always start with
use - Encapsulate only logic, not UI
- Return only what's needed (prefer returning objects)
- Use them to keep components clean and DRY