Skip to content
Sahil Durgia/ full-stack
2 min readReact

React Hooks From First Principles: Why useState and useEffect Exist

Hooks weren't added for syntax convenience — they solved a real, specific problem with how class components shared and organized stateful logic.

ReacthooksuseStateuseEffect

Before Hooks (React 16.8, 2019), state and lifecycle logic lived in class components — `this.state`, `componentDidMount`, `componentDidUpdate`. Hooks weren't added because functions are trendier than classes. They fixed a specific, real problem: class-based logic was hard to reuse and hard to organize by concern.

The real problem Hooks solved

In a class component, logic related to one concern — say, subscribing to a data source — was split across multiple lifecycle methods: subscribe in `componentDidMount`, unsubscribe in `componentWillUnmount`, re-subscribe if a prop changed in `componentDidUpdate`. Related logic, scattered by *when it runs* rather than *what it's for*. And reusing that logic across two unrelated components meant reaching for patterns like higher-order components or render props — both of which added extra wrapper layers to the component tree just to share behavior.

What useState actually is

const [count, setCount] = useState(0);
// Conceptually: React keeps a slot of state tied to this component instance.
// Calling setCount schedules a re-render; on the next render, useState
// returns the updated value from that same slot — this is exactly the
// closure mechanism from the JavaScript Fundamentals series, applied.

This is precisely why the closures post in this series matters here: a component function closes over the state React hands it for that render, and the setter closes over React's internal mechanism to schedule the next one. Hooks aren't new magic — they're closures, applied deliberately.

What useEffect actually replaced

One hook, `useEffect`, replaces `componentDidMount` + `componentDidUpdate` + `componentWillUnmount` — because all three were really the same concern ("synchronize with something outside React") artificially split across three lifecycle names. The dependency array is what tells React *when* that synchronization needs to re-run, collapsing three separate methods into one that's organized by concern instead of by timing.

Why this fixed the reuse problem

A custom hook — a plain function that calls other hooks internally — packages stateful logic into something as easy to reuse as calling a function, no wrapper components required. `useWindowWidth()`, `useDebounce()`, `useLocalStorage()` — each one is ordinary logic, extracted once, reused everywhere, with no extra layer in the component tree. That's the actual payoff Hooks were built for.

Runnable example

The snippets above are the short version — the full, runnable code lives on GitHub.

View on GitHub ★
Keep reading
Next: state management in React

Part 9 of the why-React series.