Imagine three different components that each need to track window width, fetch similar data, or subscribe to online status โ copy-pasting the same useState + useEffect logic into each one is repetitive and hard to keep in sync when it needs a fix. A custom Hook extracts that logic into one reusable function.
import { useEffect, useState } from "react";
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const goOnline = () => setIsOnline(true);
const goOffline = () => setIsOnline(false);
window.addEventListener("online", goOnline);
window.addEventListener("offline", goOffline);
return () => {
window.removeEventListener("online", goOnline);
window.removeEventListener("offline", goOffline);
};
}, []);
return isOnline;
}
// Now ANY component can use it in one line:
function StatusBadge() {
const isOnline = useOnlineStatus();
return <span>{isOnline ? "๐ข" : "๐ด"}</span>;
}Custom Hooks share logic, not state itself โ each component calling useOnlineStatus() gets its own completely independent isOnline state, they just all run the identical underlying logic.
// useLocalStorage - state that automatically persists across page reloads
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// useDebounce - delays updating a value until typing pauses
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}useDebounce directly solves the search-on-every-keystroke problem flagged back in Chapter 22 โ pair it with a search input, and API calls only fire once the user pauses typing.
React's Hook rules (like being able to call useState inside them, and the rules-of-hooks ESLint checks) only apply to functions named starting with use. Naming your function getOnlineStatus instead of useOnlineStatus means React's tooling won't recognize it as a Hook, and calling other Hooks inside it may not work correctly.
Build a custom useToggle hook that manages a boolean value with a function to flip it, then use it to control a simple show/hide panel.
import { useState } from "react";
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue(v => !v);
return [value, toggle];
}
function Panel() {
const [isVisible, toggleVisible] = useToggle(false);
return (
<>
<button onClick={toggleVisible}>Toggle Panel</button>
{isVisible && <p>Panel content!</p>}
</>
);
}
Q Can a custom Hook call other Hooks inside it?
Yes โ that's exactly the point. A custom Hook is just a regular function that's allowed to call useState, useEffect, useRef, or even other custom Hooks internally, composing them into one reusable piece of logic.
Q Where should custom Hooks live in a project's folder structure?
A common convention is a dedicated hooks/ folder inside src/ (e.g., src/hooks/useOnlineStatus.js), separate from the components/ folder, making them easy to find and reuse across the whole app.