React
React is a library for describing what the interface should look like for a given state, and letting something else work out how to get the DOM there. The model is one line — UI is a function of state — and almost every difficulty people have with React comes from breaking it: doing work during render that is not pure, or keeping state in a place that cannot be the source of truth.
- Model
- UI = f(state)
- Two phases
- Render · Commit
- Effects are for
- Outside systems
Describe, do not instruct
Before React, front-end code mostly gave instructions: find this node, set its text, add a class, remove that row. Every instruction had to be paired with the instruction that undoes it, and the number of pairs grew faster than the features did — which is why old front-end code bases tended to break in places nobody had touched.
React inverts that. You write a function that, given the current state, returns a description of the interface, and React works out the DOM operations needed to match it. You never remove the row; you describe a list that no longer contains it. The gain is that there is one description to be correct rather than a growing set of transitions between states.
The diagram splits the work into two phases, and that split is the most load-bearing idea on this page. The render phase calls your components to produce the description; it is expected to be pure, and React may run it twice, interrupt it, or throw the result away. The commit phase applies the difference to the real DOM and happens once. Effects run after the commit, when the DOM is up to date.
State: where it lives and what belongs in it
Two questions decide most React design. The first is where a piece of state should live: as low in the tree as possible, and no lower than the closest common ancestor of everything that reads it. Putting it higher than that makes unrelated parts of the app re-render; putting it lower forces you to synchronise two copies, which never fully works.
The second is what deserves to be state at all. Anything you can compute from existing state or props during render should be computed, not stored. A fullName held in state alongside firstName and lastName is a third value that can disagree with the other two, and eventually will. The test is blunt: if two pieces of state can ever contradict each other, one of them is derived and should be deleted.
A distinction worth making early is between client state and server state. Client state is genuinely yours: which tab is open, what is typed in the form, whether the menu is expanded. Server state is a cache of something that lives elsewhere and can change without you — it needs fetching, refetching, staleness rules, retries and invalidation. Trying to hold server state in useState is how components end up with five booleans for loading, error, data, stale and refetching, all managed by hand.
Effects are an escape hatch
useEffect exists to synchronise a component with something outside React: a subscription, a browser API, a chart library that owns its own DOM node, a timer. That is its whole job. React's own documentation now leads with a page called "You Might Not Need an Effect", which is a reasonable signal about how often it is reached for by default.
| You wrote an effect to… | Do this instead |
|---|---|
| Compute a value when props change | Compute it during render. If it is genuinely expensive, wrap it in useMemo — but measure first. |
| Reset state when a prop changes | Give the component a key. A new key is a new component with fresh state, and it needs no code. |
| Send a request when a button is clicked | Do it in the event handler. The cause is the click, not the render. |
| Notify a parent that state changed | Call the callback where the change happens, or lift the state so there is nothing to notify. |
| Fetch data on mount | A legitimate use, but use a data library or the framework's loader — raw effects lack cancellation, caching and race handling. |
When an effect is genuinely right, the cleanup function is not optional. It runs before the effect runs again and when the component unmounts, and it is where you unsubscribe, clear the timer and abort the request. An effect that subscribes without unsubscribing leaks a listener per render, which is invisible in development and is the thing making the tab slow after twenty minutes.
Performance, in the order that matters
React re-renders a component when its state changes, when its parent re-renders, or when a context it consumes changes. A re-render is not a DOM update — it is calling your function and comparing the result — so it is usually cheap, and most applications never need any of the memoisation tools.
The fixes are worth trying in order, because the first two are free and the third has a cost of its own. First, move state down: if only one subtree cares about a value, put it there and the rest of the tree stops re-rendering. Second, pass elements through as children rather than creating them inside a component that re-renders often — an element passed as a prop does not re-render when its parent does. Only then reach for memo, useMemo and useCallback, each of which adds a comparison on every render and only pays off when the work it skips is genuinely larger than that comparison.
One correctness rule sits inside the performance topic: keys. When React renders a list it uses the key to decide which element corresponds to which item across renders. Using the array index as a key tells React "the item in position two is the same item as before", which is false as soon as the list is reordered or something is removed from the middle — and the visible result is state and focus attaching to the wrong row. Use a stable id from the data.
Server Components, briefly
The newer model splits components into two kinds. Server Components run only on the server: they can read a database or a file directly, they never ship their code to the browser, and they cannot use state, effects or event handlers. Client Components are what React has always been, and they opt in explicitly.
The practical consequence is a bundle-size argument, not a rendering one. A date-formatting library used only in a Server Component costs the user nothing, and data fetching moves next to the component that needs it without a round trip through an API. The line to hold is that interactivity — anything with onClick, state or an effect — must live in a Client Component, so the useful pattern is a server tree with small client leaves, which is the same shape this encyclopedia itself is built in.
How this shows up in real delivery
React is a library, not a framework, and that is a real trade rather than a slogan. It gives you a component model and nothing else — routing, data fetching, forms and build setup are decisions you make and own. The freedom is genuine, and so is the cost: two React codebases in the same company can share almost no conventions, and a new joiner has to learn the assembly rather than the framework.
Where it degrades
- Derived values stored in state, which can disagree with the values they were derived from.
- Effects used to keep two pieces of state in sync, which is a rendering computation written a frame late.
- Fetching in a bare effect with no cleanup, which races and leaks.
- Array index as a key, which attaches state and focus to the wrong row after a reorder.
- Memoising everything by default, which adds comparisons on every render and hides the real cost.
- Mutating state in place, so React sees the same reference and skips the update.
- Everything in one global store, which turns every change into an application-wide event.
When to use it
Use it when
- The interface has genuine state that changes over time and in several places at once.
- You want a component model and are willing to choose routing, data and build tooling yourself.
- The hiring pool matters — this is the largest one on the front end.
- Server Components can move data fetching and heavy libraries off the client.
Avoid it when
- A content page with a little interactivity — HTML with a small script is smaller and faster.
- The team wants conventions supplied rather than chosen — that is what Angular offers instead.
- The main problem is a document, not an application — a heavy client runtime buys nothing.
- Adding it to one widget on an otherwise server-rendered page, where the runtime outweighs the widget.
Found this useful?
Share it with someone who is working on the same problem.