🪝 React Hooks Explained — useState and useEffect
Master React's useState and useEffect hooks. Learn the rules of hooks, dependency arrays, cleanup functions and the bugs they cause — with interview Q&A.
Hooks are functions that let you use React features (state, lifecycle, context) inside function components. They were added in React 16.8.
📜 The 2 Rules of Hooks
- Only call hooks at the top level — never inside loops, conditions or nested functions.
- Only call hooks from React functions — components or other hooks, not plain JS.
The ESLint plugin eslint-plugin-react-hooks enforces both.
🟣 useState
const [count, setCount] = useState(0);
setCount(count + 1); // direct
setCount(c => c + 1); // functional updater🟢 useEffect
Runs after render. Use it for side effects: fetching, subscriptions, timers, DOM manipulation.
useEffect(() => {
document.title = `Count: ${count}`;
return () => {
// cleanup — runs before next effect or on unmount
};
}, [count]); // dep array📏 Dependency array decoded
[]— run once after mount[a, b]— run after mount and whenever a or b change- omitted — run after every render
💡 Strict Mode in dev runs effects twice
To help you catch missing cleanup. Don't disable it — fix the effect.
💻 Code Examples
Counter with useState
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
);
}Output:
Each click increments the counter and re-renders.
Fetch with useEffect + cleanup
useEffect(() => {
const ctrl = new AbortController();
fetch(`/api/users/${id}`, { signal: ctrl.signal })
.then(r => r.json())
.then(setUser)
.catch(err => { if (err.name !== 'AbortError') console.error(err); });
return () => ctrl.abort(); // cancel on unmount or id change
}, [id]);Output:
Refetches when id changes; cancels in-flight request on unmount.
Setting an interval (with cleanup)
useEffect(() => {
const id = setInterval(() => setTick(t => t + 1), 1000);
return () => clearInterval(id);
}, []);Output:
Interval starts on mount, cleared on unmount.
⚠️ Common Mistakes
- Calling a hook inside an `if` — breaks React's hook-order assumption.
- Forgetting the dependency array — effect runs every render and can cause infinite loops.
- Adding objects/functions to deps without memoizing — they're new every render and re-trigger the effect.
- Forgetting cleanup — leads to leaks (intervals, listeners, subscriptions).
- Using state right after setState expecting the new value (it's still the old closure).
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What are React hooks?Beginner
Functions that let you use React features (state, lifecycle, context, refs) in function components. Introduced in React 16.8 to replace class components for most use cases.
Q2.What are the rules of hooks?Beginner
1) Only call hooks at the top level — never inside conditions, loops, or nested functions. 2) Only call hooks from React function components or other hooks. This guarantees React can match hook calls to state slots by call order.
Q3.What does the useEffect dependency array do?Intermediate
It tells React when to re-run the effect. Empty `[]` = once after mount. `[a, b]` = whenever a or b change (using Object.is comparison). Omit it = after every render (rarely what you want).
Q4.When does the cleanup function run?Intermediate
Before the next effect runs (when deps change) and when the component unmounts. Use it to cancel timers, abort fetches, remove listeners, close subscriptions.
Q5.Why does useEffect run twice in development?Advanced
React 18 Strict Mode intentionally mounts → unmounts → mounts components in dev to surface bugs in cleanup logic. It doesn't happen in production. The fix is correct cleanup, not disabling Strict Mode.
Q6.What's the difference between setCount(count + 1) and setCount(c => c + 1)?Advanced
Direct form uses the captured `count` from the current render (closure). Functional form receives the latest queued state — safer when updates batch or when you call setState multiple times in one render.
🧠 Quick Summary
- Hooks unlock state and lifecycle in function components.
- Top-level only, React functions only.
- useState returns [value, setter]; functional setter avoids stale closures.
- useEffect runs after render; deps decide when.
- Always cleanup intervals, listeners, fetches and subscriptions.
- Strict Mode double-fires effects in dev — a feature, not a bug.