Reusable Logic, Same Rules as Built-In Hooks
Custom Hooks
A custom hook is a function whose name starts with `use` that calls other hooks. Same rules apply — just extract and reuse.
What you'll learn
- Extract reusable logic into a custom hook
- Compose hooks inside other hooks
- Name conventions and rules
A custom hook is a function whose name starts with use and
that calls other hooks. That’s the whole definition. Custom hooks
are the React way to reuse stateful logic between components.
A Simple Custom Hook
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
function on() { setOnline(true); }
function off() { setOnline(false); }
window.addEventListener("online", on);
window.addEventListener("offline", off);
return () => {
window.removeEventListener("online", on);
window.removeEventListener("offline", off);
};
}, []);
return online;
}
// Use it like any other hook
function StatusBadge() {
const online = useOnlineStatus();
return <span>{online ? "🟢 Online" : "🔴 Offline"}</span>;
} Same useState/useEffect/etc. you already know — just wrapped in
a function so any component can call it.
What Belongs in a Custom Hook
If two components have the same useState + useEffect setup, that’s
a custom hook waiting to happen. Common examples:
useOnlineStatus— listen to network changesuseLocalStorage— sync state with localStorageuseFetch— fetch data with loading + erroruseDebounce— debounce a valueuseMediaQuery— track a CSS media queryusePrevious— remember the previous render’s value
With Arguments
function useDebounce(value, ms = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), ms);
return () => clearTimeout(id);
}, [value, ms]);
return debounced;
}
// Usage:
function Search() {
const [query, setQuery] = useState("");
const debounced = useDebounce(query, 250);
useEffect(() => { fetch(`/api/q?q=${debounced}`); }, [debounced]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
} Returning Multiple Values
Return an array (state-style) for ergonomics, or an object for clarity:
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn(o => !o), []);
return [on, toggle]; // array — destructure with custom names
}
const [open, toggleOpen] = useToggle(); function useUser(id) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
// ... fetch logic ...
return { data, error, loading }; // object — clearer when you have several values
}
const { data, error, loading } = useUser(id); Custom Hooks Are NOT Magic
They share code, not state. Each component that calls
useOnlineStatus() gets its own state.
If you want components to share the same state, lift it into a parent (or use context, or a store).
Rules Still Apply
A custom hook IS a hook — same rules:
- Name starts with
useso the linter can check it - Called at the top level (not in
if/for) - Called from components or other hooks only
Up Next
A built-in hook for generating stable, SSR-safe ids.
useId →