Loading lessons...
Extracting Reusable Hook Logic
Extracting Reusable Hook Logic
Let's explore building a widely useful custom hook: useFetch, designed to encapsulate API fetching, loading states, and error handling into a single reusable unit.
Building a Reusable useFetch Hook
// src/hooks/useFetch.js
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url)
.then(res => {
if (!res.ok) throw new Error("HTTP Error! Status: " + res.status);
return res.json();
})
.then(json => {
if (isMounted) {
setData(json);
setError(null);
}
})
.catch(err => {
if (isMounted) setError(err.message);
})
.finally(() => {
if (isMounted) setLoading(false);
});
return () => {
isMounted = false; // Prevent updating state on unmounted components
};
}, [url]);
return { data, loading, error };
}
Using useFetch Across Multiple Components
Now any component in your application can fetch data with a single line of code!
``jsx // Component 1: Product Showcase function ProductList() { const { data: products, loading, error } = useFetch('/api/products');
if (loading) return <p>Loading products...</p>; if (error) return <p>Error: {error}</p>;
return ( <ul> {products.map(p => <li key={p.id}>{p.name} - ${p.price}</li>)} </ul> ); }
// Component 2: User Directory function UserDirectory() { const { data: users, loading } = useFetch('/api/users');
if (loading) return <p>Loading users...</p>;
return <div>Total Users: {users ? users.length : 0}</div>; }
## Summary & TL;DR
- Custom hooks hide complex lifecycle management behind simple return signatures (e.g. `{ data, loading, error }`).
- Returning objects or arrays from custom hooks provides flexible destructuring options for consumer components.
- Common custom hook patterns include `useFetch`, `useLocalStorage`, `useDebounce`, and `useForm`.