Custom Hooks
Custom hooks let you extract and reuse stateful logic across components. A custom hook is a plain JavaScript function whose name starts with use and that can call other hooks.
useFetch โ Data Fetching
// hooks/useFetch.js
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
let cancelled = false;
setLoading(true); setError(null);
fetch(url)
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.then(d => { if (!cancelled) setData(d); })
.catch(e => { if (!cancelled) setError(e.message); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage โ component has zero async logic
function UserList() {
const { data: users, loading, error } = useFetch("/api/users");
if (loading) return <Spinner />;
if (error) return <p>Error: {error}</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
useLocalStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch { return initialValue; }
});
const set = (val) => {
const next = val instanceof Function ? val(value) : val;
setValue(next);
localStorage.setItem(key, JSON.stringify(next));
};
return [value, set];
}
// Usage โ persists across page refreshes
function ThemePicker() {
const [theme, setTheme] = useLocalStorage("theme", "light");
return <button onClick={() => setTheme(t => t === "light" ? "dark" : "light")}>{theme}</button>;
}
useDebounce
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
}
// Usage โ prevents API call on every keystroke
function SearchBox() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 400);
const { data } = useFetch(debouncedQuery ? `/api/search?q=${debouncedQuery}` : null);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
{data?.map(r => <div key={r.id}>{r.title}</div>)}
</div>
);
}
TipName your custom hooks with the
use prefix โ this is not just a convention, it lets the React linter (eslint-plugin-react-hooks) check the rules of hooks inside your custom hook.