Hooks let functional components manage state (useState), run side effects (useEffect), access refs (useRef), and extract reusable logic into custom hooks — all following strict rules about call order.
Only call at top level (no loops/conditions) and only in React functions. React depends on consistent call order to match hooks to their state.
Runs after render. Dependency array controls re-runs: no array = every render, [] = mount only, [deps] = when deps change. Return cleanup function.
Mutable .current container that persists across renders without triggering re-renders. Used for DOM access and instance variables.
Extract reusable stateful logic into use-prefixed functions — the modern replacement for HOCs and render props.
Cache values and function references between renders — optimization tools for memoized components, not defaults for every computation.
Hooks (introduced in React 16.8) are functions that let you use React features in functional components. They replace class component patterns with a simpler, more composable API.
Two rules must be followed for hooks to work correctly:
The eslint-plugin-react-hooks enforces these rules automatically.
const [value, setValue] = useState(initialValue) declares a state variable. The setter triggers a re-render. For expensive initial values, pass a function: useState(() => computeExpensiveValue()) — the function runs only on the first render. The setter accepts a direct value or an updater function: setValue(prev => prev + 1). Use the updater form when the new state depends on the previous state to avoid stale closure bugs.
useEffect(setup, dependencies?) runs side effects after the component renders. The setup function connects to external systems (APIs, subscriptions, DOM manipulation). The optional dependency array controls when the effect re-runs:
[]: runs once after mount[a, b]: runs when a or b changeReturn a cleanup function to disconnect: useEffect(() => { const sub = subscribe(); return () => sub.unsubscribe(); }, []). Cleanup runs before the effect re-runs and when the component unmounts.
Common patterns: fetching data on mount, subscribing to events, setting up timers, updating document title.
const ref = useRef(initialValue) creates a mutable container with a .current property that persists across renders without triggering re-renders when changed. Two primary uses:
ref to a JSX element's ref attribute to get the DOM node. ref.current is the actual DOM element after render.Key difference from state: updating ref.current does not cause a re-render. Don't read or write refs during rendering — only in event handlers and effects.
useMemo(() => expensiveComputation(a, b), [a, b]) caches a computed value, recomputing only when dependencies change. useCallback(fn, [deps]) caches a function reference — equivalent to useMemo(() => fn, [deps]). Use these for optimization when passing values/callbacks to memoized child components (React.memo), not as a default for every value.
Custom hooks extract reusable stateful logic into functions prefixed with use. They can call any other hooks and return any values.
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}Common patterns: useFetch (data fetching), useDebounce (debounced values), useMediaQuery (responsive breakpoints), useLocalStorage (persistent state), useOnClickOutside (click-away detection).
Hooks must be called in the same order every render (no conditional calls). useState manages state, useEffect handles side effects with cleanup, useRef provides mutable containers without re-renders. Custom hooks are the modern replacement for HOCs and render props for logic reuse. useMemo/useCallback are performance optimizations, not defaults.
Fun Fact
The Rules of Hooks exist because React stores hook state in an ordered array internally. When a component renders, React walks through the array in order, matching each useState/useEffect call to its stored value. If you put a hook inside a condition, the array positions shift and hooks get the wrong state — which is why the order must be identical on every render.