The problem: repeated logic
Imagine two components that need a switch (on/off). In both you would write the same thing:
const [open, setOpen] = useState(false);
const toggle = () => setOpen((v) => !v);
Copying and pasting that logic works, but it drifts out of sync over time. React's solution is custom hooks.
What a custom hook is
A custom hook is, simply, a function whose name starts with use and
that uses other hooks inside. It encapsulates stateful logic and returns
what the component needs:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
Now any component reuses that logic without duplicating it:
function Panel() {
const [open, toggle] = useToggle(false);
return (
<div>
<button onClick={toggle}>Show/hide</button>
{open && <p>Visible content</p>}
</div>
);
}
The key point: each component that calls useToggle gets its own state,
isolated. You share the logic, not the state.
Another example: useCounter
A hook can compose several hooks and expose whatever API is most convenient (an object, an array, functions...):
function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = () => setCount((c) => c + 1);
const reset = () => setCount(initial);
return { count, increment, reset };
}
The rules of hooks
So that React can associate each call with its state, hooks have two unbreakable rules:
- Call them only at the top level. Never inside
if, loops, conditionals or nested functions. The order of the calls must be the same on every render. - Call them only from React components or from other custom hooks. Not from regular JavaScript functions.
// ❌ BAD: hook inside an if (the order changes between renders)
if (active) {
const [x, setX] = useState(0);
}
// ✅ GOOD: at the top level; the condition goes inside
const [x, setX] = useState(0);
if (active) { /* use x */ }
A custom hook is just a naming convention (
use...) plus these rules. There is no "magic": it is a function that organizes your hooks.
Examples
A reusable useToggle hook
function useToggle(initial) {
const [value, setValue] = React.useState(initial);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
// Simulation: two "instances" have independent state
let state1 = false, state2 = true;
console.log("start:", state1, state2);