Hooks Overview
What hooks are, why they replaced classes, how React tracks them by call order, and the two Rules of Hooks that keep that tracking honest — the foundation every other hook builds on.
What is a hook (start here). A hook is a special function whose name starts with use that lets a plain function component hook into React features it otherwise couldn't touch — state that survives re-renders (useState), side effects tied to the render lifecycle (useEffect), memoized values (useMemo), refs (useRef), context (useContext), and more. Before hooks (React 16.8, early 2019), only class components could hold state or run lifecycle code. Hooks let a function — the simplest thing in JavaScript — own everything a class used to.
Why hooks replaced classes. In class components, related logic was scattered across lifecycle methods: you'd set up a subscription in componentDidMount, update it in componentDidUpdate, and tear it down in componentWillUnmount — three methods for one concern, with unrelated concerns interleaved in each. Reusing stateful logic meant awkward Higher-Order Components or render props that produced 'wrapper hell'. And this binding was a perennial footgun. Hooks fix all three: related logic lives together in one effect (with its own cleanup), reusable logic extracts into a custom hook, and there is no this.
How React actually tracks hooks (the key mechanic). React does not know the names of your hooks — it identifies each one purely by call order. On the first render of a component, React walks your hook calls top to bottom and allocates a slot for each: slot 0, slot 1, slot 2. On every subsequent render it walks them again in the same order and hands back the state stored in the matching slot. There is a per-component linked list of hook state, and the render pointer advances one node per hook call. This is why order must be identical every single render.
Rule #1 — call hooks only at the top level. Never call a hook inside a loop, a condition, a nested function, or after an early return. If you wrap useState in an if that is true on render 1 and false on render 2, the slot indices shift: what was slot 1 becomes slot 0, and React hands the wrong state to the wrong hook — silently corrupting your component. Keeping every hook at the top level guarantees the call order is stable, so slot N always means the same hook.
Rule #2 — call hooks only from React functions. Call hooks from React function components or from other custom hooks — never from plain utility functions, class methods, or event handlers. Only during React's render of a component is there an active 'current component' whose hook list can be advanced; outside that window there is no slot list to attach to, so the call is meaningless (and React throws 'Invalid hook call').
The naming convention is load-bearing. The use prefix is not decoration — eslint-plugin-react-hooks uses it to decide which functions to lint as hooks. Name a hook getUser instead of useUser and the linter stops enforcing the Rules of Hooks inside it; name a normal function useThing and the linter will wrongly demand hook rules. Follow the convention exactly so tooling can protect you.
Enforce it with tooling, not discipline. Install eslint-plugin-react-hooks and enable both rules: rules-of-hooks (catches conditional/looped/misplaced hook calls) and exhaustive-deps (catches missing dependency-array entries). Treat their warnings as build-breaking errors. Almost every subtle hook bug — stale closures, effects that never re-run, corrupted state — is something one of these two rules would have caught.
Hooks are just functions that compose. Because a hook is an ordinary function that may call other hooks, you can build your own by combining the built-ins: a useAuth that reads context and subscribes to a store, a useFetch that pairs useState with useEffect. Composition is the whole point — the built-in hooks are Lego bricks, and custom hooks are the models you assemble from them (covered in depth in the Custom Hooks topic).
Common gotcha — conditional rendering is fine, conditional hooks are not. You can absolutely render different UI based on a condition; you just cannot call a hook conditionally. If a hook only sometimes applies, always call it and put the condition inside the hook (e.g. pass enabled into the effect, or guard the effect body), rather than wrapping the use... call itself in an if.
The mental model (memorise this). React tracks hooks by call order, not by name — think of a numbered row of lockers assigned on first render. Rule #1 (top level only) keeps the locker numbers stable across renders; Rule #2 (components/custom hooks only) guarantees there is a locker row to use at all. Keep the order identical every render, let ESLint police it, and every other hook behaves predictably.
Hooks are like field injection in a Spring bean: the framework wires state and lifecycle into your component the way the container wires dependencies into a bean. The Rules of Hooks are like the contract that injection only works on container-managed beans — call `new MyService()` yourself (a plain function) and nothing is wired, exactly like calling a hook outside a component. React's call-order slot tracking is like an ordered @PostConstruct/@PreDestroy lifecycle registry: reorder or conditionally skip a step and the framework's bookkeeping desyncs. Custom hooks are the equivalent of extracting a reusable @Component/@Service that other beans depend on.
- A hook is a use-prefixed function that lets a function component tap into React state and lifecycle; before hooks only classes could.
- React identifies each hook by call order (slot index), not by name, using a per-component linked list of state.
- Rule of Hooks #1: call hooks only at the top level - never in loops, conditions, nested functions, or after an early return.
- Rule of Hooks #2: call hooks only from React function components or from other custom hooks, never from plain functions.
- Conditional calls shift slot indices, so React hands the wrong state to the wrong hook and silently corrupts the component.
- The use prefix is how eslint-plugin-react-hooks decides what to lint as a hook - the naming convention is functional, not cosmetic.
- Hooks replaced classes because they colocate related logic, avoid this binding, and make stateful logic reusable without HOC wrapper hell.
- You can render conditionally, but never call a hook conditionally - move the condition inside the hook instead.
- Enable rules-of-hooks and exhaustive-deps and treat their warnings as errors; they catch nearly every subtle hook bug.
- Hooks compose: a custom hook is just a function that calls other hooks, so the built-ins are building blocks.
Worked Code
// CORRECT: every hook at the top level of a component, same order each render
function Profile({ userId }) {
const [name, setName] = useState(""); // slot 0
const [age, setAge] = useState(0); // slot 1
useEffect(() => { /* ... */ }, [userId]); // slot 2
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
// WRONG #1: hook inside a condition - slot indices shift between renders
function Broken({ loggedIn }) {
if (loggedIn) {
const [name, setName] = useState(""); // NEVER: sometimes slot 0, sometimes skipped
}
const [count, setCount] = useState(0); // its slot changes when loggedIn flips -> corrupt
}
// FIX: always call the hook, put the condition INSIDE
function Fixed({ loggedIn }) {
const [name, setName] = useState(""); // always slot 0
const displayName = loggedIn ? name : "guest"; // condition lives in normal code
return <span>{displayName}</span>;
}// WRONG: calling a hook from a plain helper function
function formatUser() {
const [x] = useState(0); // Invalid hook call - there is no current component
return x;
}
// CORRECT: a custom hook (use-prefixed) may call other hooks
function useUser(id) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch("/api/users/" + id)
.then((r) => r.json())
.then((u) => { if (!cancelled) setUser(u); });
return () => { cancelled = true; };
}, [id]);
return user; // components consume this like any built-in hook
}// BEFORE: logic scattered across three lifecycle methods, plus 'this' binding
class Clock extends React.Component<{}, { now: Date }> {
timer?: number;
state = { now: new Date() };
componentDidMount() { this.timer = window.setInterval(this.tick, 1000); }
componentWillUnmount() { clearInterval(this.timer); } // teardown lives far away
tick = () => this.setState({ now: new Date() }); // arrow to bind 'this'
render() { return <span>{this.state.now.toLocaleTimeString()}</span>; }
}
// AFTER: one effect colocates setup + teardown, no 'this', no scatter
function Clock() {
const [now, setNow] = useState(() => new Date());
useEffect(() => {
const timer = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(timer); // cleanup sits next to setup
}, []);
return <span>{now.toLocaleTimeString()}</span>;
}{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error"
}
}▶Try It Live
Edit the code and press Run — it executes safely in a sandboxed iframe. Use the Console tab for log output.
Interview-Ready Q&A
Two rules: (1) call hooks only at the top level - never in loops, conditions, nested functions, or after an early return; (2) call hooks only from React function components or from other custom hooks. They exist because React tracks hook state purely by call order (slot index), not by name. If a hook is skipped or reordered conditionally, the slot indices shift and React hands the wrong state to the wrong hook. The rules-of-hooks and exhaustive-deps ESLint rules enforce them.
- 1React tracks hooks by call order (slot index), never by name.
- 2Rule #1: hooks only at the top level - no loops, conditions, nested functions, or after early return.
- 3Rule #2: call hooks only from components or other custom hooks.
- 4Conditional hook calls shift slots and silently corrupt state.
- 5Render conditionally if you like, but never call a hook conditionally.
- 6The use prefix is what makes the linter treat a function as a hook.
- 7Enable rules-of-hooks and exhaustive-deps and treat them as errors.
- 8Hooks colocate logic, drop this binding, and make stateful logic reusable.
- 9A custom hook is just a function that composes other hooks.
- 10Let ESLint enforce the rules for you instead of relying on discipline.