Why React Hooks Exist: The Problem They Were Built to Solve
Class components had three problems that grew worse over time: logic scattered across lifecycle methods, stateful logic impossible to reuse, and components that were hard to understand. Hooks are the answer to all three.
In vanilla JavaScript, when a value changes on screen, you grab the element and change its content directly. That works fine. But React operates on a fundamentally different model.
React controls when and how your UI updates. You do not touch the DOM manually — React does that for you, in response to state changes. The moment you start grabbing DOM elements and changing them yourself inside a React component, you step outside the system. React has no idea the change happened, so it cannot account for it in future renders.
State is the bridge between data and UI in React. A state is a variable whose change automatically tells React: something is different, re-render this component.
What Hooks Are
Before hooks (React 16.7 and earlier), stateful logic and side effects could only live in class components — through this.setState, componentDidMount, componentDidUpdate, and componentWillUnmount. That created three compounding problems:
Scattered logic. Related code was split across multiple lifecycle methods. Fetching data on mount, cleaning up on unmount, and responding to prop changes in update — all in different places, impossible to read as a unit.
No stateful reuse. If two components needed the same stateful logic — say, tracking window size — you duplicated it, or reached for patterns like higher-order components and render props that added layers of nesting without solving the underlying problem.
Complex components. As class components grew, they became walls of lifecycle code where unrelated logic sat next to each other and related logic was spread apart.
Hooks let you use state and side effects directly in function components. They also let you extract stateful logic into a function — a custom hook — and reuse it across any component without changing the component tree.
Every Hook and What It Solves
| Hook | What it solves |
|---|---|
useState | Reactive state — values that drive re-renders when they change |
useReducer | Complex state with multiple related actions — one function owns all transitions |
useEffect | Synchronizing with the outside world after render |
useLayoutEffect | DOM measurement and mutation before the browser paints |
useInsertionEffect | Injecting styles before DOM mutations — for CSS-in-JS libraries |
useRef | Mutable values that persist across renders without causing re-renders; direct DOM access |
useImperativeHandle | Controlling exactly what a parent can access on a child's ref |
useContext | Sharing state across a component tree without prop drilling |
useSyncExternalStore | Subscribing to external stores safely under concurrent rendering |
useId | Stable, unique IDs that match between server and client |
useTransition | Marking a state update as non-urgent to keep interactions responsive |
useDeferredValue | Deferring a value to reduce the cost of expensive downstream renders |
The hooks every React developer reaches for daily are useState, useEffect, and useContext. The ones that earn their place as applications grow are useReducer, useRef, useTransition, and useDeferredValue. The specialized ones — useImperativeHandle, useLayoutEffect, useInsertionEffect, useSyncExternalStore — exist for narrow but real problems, and knowing when those problems appear is what separates confident React work from guesswork.
Each post in this series covers one hook in depth: how it works under the hood, when to reach for it, what problems it causes when misused, and what alternatives exist. This post is the map. Each of those is the territory.
Practice what you just read.
Keep reading