Custom Hooks
Extracting and reusing stateful logic between components - what makes a custom hook, naming conventions, and real-world examples.
What it is, in plain English: A custom hook is a JavaScript function whose name starts with 'use' that calls other hooks inside it. It lets you extract component logic -- state, effects, subscriptions -- into a reusable function, without changing the underlying behaviour. Unlike a regular function, a custom hook can use useState, useEffect, and other hooks because React tracks it as part of the calling component's hook chain. Custom hooks are how the React community shares reusable logic.
Extracting reusable logic into a function
If two components need the same stateful logic -- the same useState, the same useEffect pattern -- copying and pasting it is a maintenance trap. A custom hook lets you write that logic ONCE, as a function, and use it in as many components as you need.
The rule: name it "use..." and it can call other hooks
// A custom hook is JUST a function -- the "use" prefix is a convention
// that tells React (and the linter) "this function uses hooks inside it":
function useCounter(initialValue = 0) {
const [count, setCount] = React.useState(initialValue);
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
// Now ANY component can reuse this counter logic:
function CounterA() {
const { count, increment, decrement } = useCounter(0);
return (
<div>
{count}
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
function CounterB() {
const { count, reset } = useCounter(100); // independent instance, different initial value
return <div>{count} <button onClick={reset}>Reset</button></div>;
}A real example -- syncing with localStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = React.useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
function setStoredValue(newValue) {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}
return [value, setStoredValue];
}
// Usage looks exactly like useState, but persists automatically:
function Settings() {
const [theme, setTheme] = useLocalStorage("theme", "light");
return <button onClick={() => setTheme("dark")}>{theme}</button>;
}What custom hooks share -- and what they DON'T share
// Each component that calls a custom hook gets its OWN independent state:
function App() {
return (
<>
<CounterA /> {/* its own count, starts at 0 */}
<CounterB /> {/* its own count, starts at 100 */}
</>
);
}
// Custom hooks share LOGIC (the code), not STATE (the data).
// Calling useCounter() twice creates two completely separate counters.
Try It Yourself (JSX)
Official Sources
- React Documentation — Reusing Logic with Custom Hooks
- React Documentation — Rules of Hooks
- Testing Library — React Hooks Testing
The Codex links only to official documentation. No third-party tutorials.