1. What Are React Hooks & Why Do We Need Them?
Before React 16.8, you had to write complex ES6 Class Components to manage state or lifecycle methods (`componentDidMount`, `componentWillUnmount`). React Hooks allow functional components to use state, effects, refs, and context directly using simple functions.
No more confusing `.bind(this)` or constructor boilerplate.
Extract stateful logic into custom hooks without changing component hierarchy.
Keep event listeners and their cleanups in a single `useEffect` block.
3. The Rules of Hooks (Strict & Essential)
React relies on the exact order in which Hooks are called on every render to match state with the correct internal memory cell. To maintain this contract, you must follow two strict rules:
- Rule 1: Only call Hooks at the top level of React function components or custom Hooks.
Do NOT call Hooks inside:- Conditions (
if (condition) { useState() }) - Loops (
for,while,items.forEach()) - Nested functions (e.g. inside an
onClickhandler or callback) try / catch / finallyblocks
- Conditions (
- Rule 2: Only call Hooks from React functions. Call them only inside React functional components or your own Custom Hooks (functions starting with
use). Never call Hooks inside regular vanilla JavaScript helper functions or class components.
eslint-plugin-react-hooks will automatically warn you in your editor if you accidentally violate these rules.4. useState: Local Component State
useState declares a state variable that persists between renders. Calling the setter function schedules a re-render with the new value.
export function Counter() {
const [count, setCount] = useState(0);
// Functional update: use when next state depends on current state
const handleAdd = () => setCount(prev => prev + 1);
return <button onClick={handleAdd}>Count: {count}</button>;
}
5. useEffect: Synchronizing With External Systems
In modern React, useEffect is specifically designed to synchronize your component with external systems (browser APIs, network requests, timers, DOM event listeners, or third-party libraries).
onClick or onSubmit!).| Dependency Array | Synchronization Behavior | External System Example |
|---|---|---|
useEffect(fn, []) | Runs once after initial component mount | Setting up a global window.addEventListener or WebSocket connection |
useEffect(fn, [userId]) | Runs on mount + whenever userId changes | Syncing document title or fetching data for a specific user ID |
useEffect(fn) | Runs after every single render | Syncing non-React third-party DOM widgets that require refresh on every change |
// Synchronize with browser timer API
const timer = setInterval(() => console.log("Tick"), 1000);
// Cleanup function: cleans up external subscription when component unmounts
return () => clearInterval(timer);
}, []);
6. useRef: DOM Access & Silent Persistence
useRef returns a mutable object with a .current property. It has two main uses:
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();
// 2. Mutable value that persists across renders without triggering a re-render
const timerIdRef = useRef(null);
7. useContext: Global State Without Prop Drilling
const ThemeContext = createContext("dark");
// 2. Provide Context at top level
<ThemeContext.Provider value="emerald">
<App />
</ThemeContext.Provider>
// 3. Consume directly in any child component
const theme = useContext(ThemeContext);
8. useMemo & useCallback (Brief Performance Intro)
useMemo caches the result of an expensive calculation between renders. useCallback caches a function definition to avoid breaking reference equality for child components.
useMemo or useCallback. Only reach for manual memoization when profiling reveals a specific, measurable performance bottleneck.const filteredList = useMemo(() => filterMassiveDataset(data, query), [data, query]);
// useCallback: preserves function reference between renders
const handleSelect = useCallback((id) => setSelectedId(id), []);
10. Custom Hooks Architecture
Custom Hooks allow you to extract and share stateful logic between components without duplicating code:
export function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
11. Choosing the Right Hook (Cheat Sheet)
| What you need to accomplish | The Recommended Hook |
|---|---|
| Store dynamic data that updates the UI when changed | useState |
| Synchronize with external systems (APIs, timers, event listeners) | useEffect |
| Access real DOM nodes directly or hold silent persistent values | useRef |
| Access shared global data (themes, auth) without prop drilling | useContext |
| Extract and share stateful business logic across components | Custom Hook (use...) |
| Cache a heavy calculation if profiling shows lag | useMemo (or let React Compiler handle it) |
12. Common Hooks Mistakes
- Using useEffect for user click actions: Network requests triggered by button clicks should live inside the
onClickhandler, not in an effect. - Using useEffect for derived state: Don't use state + effect to compute full name from first and last name; simply calculate
const fullName = firstName + " " + lastName;during render. - Calling Hooks conditionally: Never put hooks inside
ifblocks ortry/catch. - Missing effect cleanups: Forgetting to return a cleanup function when setting intervals or event listeners leads to memory leaks.
13. Building Practical Hook-Based Components & Best Practices
- Keep effects minimal and focused: Each effect should manage one synchronization relationship.
- Prefix custom hooks with
use: Enables linter rule verification. - Treat state as immutable: Never mutate state or ref objects directly during render.