UI Is a Function of State
What you'll learn
- The equation at React's core: UI = f(state)
- Why "render" means nothing more (and nothing less) than calling f again
- How the pure-function framing makes UIs predictable, testable, and time-travel-able
- The honest answer to "isn't recalculating everything wasteful?"
If you had to compress all of React into one line, it would be this:
UI = f(state)
Read it as: the UI is what you get when you evaluate your components at a given state. This chapter takes that equation seriously and follows where it leads, because once you believe it, most of React's design stops looking arbitrary and starts looking inevitable.
The equation, unpacked
f is your component tree. state is all the data that can change. UI is the description of what should be on screen.
Jargon: "state". The data that can change over the lifetime of the UI: form inputs, fetched records, toggles. When state changes, the UI must change, that's the whole job. Jargon: "render". Evaluating
f: calling your component functions with the current props and state and collecting the UI description they return.
One promise makes this powerful: f is a pure function. Same state in → same UI description out. No peeking at hidden globals, no rolling dice, no calling the network in the middle. (The Components and Purity chapter enforces this in detail; here we get to exploit it.)
One component, three states
export default function WeatherBadge({ temp }) {
let label;
let color;
if (temp < 0) {
label = 'Freezing';
color = 'deepskyblue';
} else if (temp < 20) {
label = 'Mild';
color = 'seagreen';
} else {
label = 'Hot';
color = 'orangered';
}
return (
<span style={{ color, fontWeight: 'bold' }}>
{`${label}: ${temp}°C`}
</span>
);
}
Feed it three different states, the same function, three different outputs:
| Input | Output description |
|---|---|
temp = -5 | blue bold text: "Freezing: -5°C" |
temp = 12 | green bold text: "Mild: 12°C" |
temp = 33 | red bold text: "Hot: 33°C" |
What happens:
- React calls
WeatherBadge({ temp: -5 }). The firstifbranch runs. - The function returns a small description: a
span, this style, this text. - Later, the temperature changes to
12. React does not patch the old description by hand, it calls the function again with{ temp: 12 }. - A different branch runs; a different description comes out. React puts that on screen.
Step 3 is the mindset shift of the whole chapter: React never edits your UI into shape. It re-asks the question.
"Re-render" just means "call f again"
When people say a component "re-rendered," they mean one precise thing:
React called your function again with newer state and collected the fresh description it returned.
Not "the browser repainted." Not "the DOM was rebuilt." Just: f ran again. Whether anything on screen changes is a separate, later question, that's the Render and Commit chapter.
So the life of a React app is this loop, running once per state change:
Pseudocode model, not real source:
while (true) {const state = getState(); // current dataconst ui = f(state); // call your components: fresh descriptionsyncToScreen(ui); // make the screen match the description}
The real engine doesn't literally spin a while loop, it waits for state changes and then runs one iteration. But the shape is exactly this: read state, compute UI, sync, repeat. The syncToScreen step is where React is smart (that's Part 2); the f(state) step is where React is simple, and that simplicity is what you're buying.
Why this framing is a superpower
1. Predictable: the UI can't drift from the state
If UI is f(state) and f is pure, then for any state there is exactly one correct UI. Not "the UI, unless some handler forgot an update", one answer, guaranteed. The entire drift bug class from the previous chapter is gone by construction.
2. Testable: it's just a function call
Because rendering is a function call that returns data, you can test UI without a browser:
import WeatherBadge from './WeatherBadge';
// Call the component directly and inspect the description it returns
const cold = WeatherBadge({ temp: -5 });
console.log(cold.props.style.color); // 'deepskyblue'
console.log(cold.props.children); // 'Freezing: -5°C'
const hot = WeatherBadge({ temp: 33 });
console.log(hot.props.children); // 'Hot: 33°C'
What happens: WeatherBadge(...) runs like any function and returns an element object (the next chapter makes these objects official). Your assertions run against plain data, no DOM, no browser, no test doubles. Real test suites use renderers for fuller checks, but the principle is identical: a pure function of state is the most testable artifact in software.
3. Time-travel debugging: states are saveable, so UIs are replayable
If UI = f(state), then a log of states is a log of every UI your app ever showed. Record each state change, then re-apply an old state, f reconstructs that exact past screen. That's the idea behind Redux DevTools' time travel and React's own inspection tooling. It only works because f is pure: replaying a state must reproduce the UI exactly, with no hidden variables sneaking in.
The pushback: "isn't recalculating everything wasteful?"
Every beginner asks this, and it deserves a real answer, not a hand-wave:
- If recalculating meant touching the DOM, it would be wasteful. DOM writes trigger layout and paint, genuinely expensive operations.
- But
fdoesn't touch the DOM. It produces descriptions: small, plain JavaScript objects. (The next chapter, "Elements Are Just Objects," shows they're literally{ type, key, props }.) Creating a few hundred tiny objects costs microseconds. - React then diffs the new description against the previous one and applies only the differences to the real DOM, usually a handful of writes, often zero. That diff is the subject of Part 2.
Analogy: re-sketching a floor plan on paper is cheap; moving walls is expensive. React re-sketches the plan on every state change (cheap, pure, discardable) and moves only the walls that actually moved (expensive, minimized).
So yes, React "recalculates everything" in the cheap medium, precisely so it can change almost nothing in the expensive one.
Diagram
Rendered diagram (PNG hi-res):
Rendered diagram (PNG hi-res):
Common misconceptions
- Misconception: "Render" means painting pixels. Reality: render means calling your functions to get a description. Paint happens later, only if the description actually changed something, and it's the browser that paints, not React.
- Misconception:
fis one specific function. Reality:fis your whole component tree composed, React calls the root, which returns descriptions of children, which get called in turn. - Misconception: State means
useState. Reality: state is any data that can change, including values handed down from parents (props are just state passed along), context, or external stores. - Misconception: Pure means "no logic allowed." Reality: pure means no side effects, branches, loops, math, and formatting are all fine inside
f. - Misconception: Recomputing the description is the expensive part. Reality: object creation is microseconds; DOM writes are the expensive part, and the diff exists to shrink those.
Why it works this way
- Functions are the best-understood building block in programming. Inputs, outputs, composition, no lifecycle riddles. If UI is a function, every tool for reasoning about functions applies to UI.
- Purity makes renders discardable. If computing a description has no side effects, React can pause, restart, or throw away a render, the foundation of the concurrent features in Part 4.
- A replayable function enables tooling. Time travel, hot reload, server rendering, and snapshot testing all reduce to "call
fat a chosen state, anywhere." - The cheap/expensive split drives the whole architecture. Recreate descriptions freely, diff them, touch the real UI minimally, every optimization in Part 2 is a refinement of this one trade.
Try it yourself
- Take any small component you've written. Call it as a plain function in a test file or console with two different prop sets and log the results. Confirm: same input → identical output, every time.
- Add a
console.log('rendered with', temp)insideWeatherBadge's body. Render it and changetempa few times. Observe: one log per state change, the function really is re-called from the top each time. - Build a two-component tree, an
Appthat rendersWeatherBadge, and log in both bodies. Change state inApp. Predict which functions re-run, and in what order, before you look. (Parent first, then child.) - Sketch a state log: in a small counter app, push
{ count }into an array on every change. Then add a "time travel" button that re-applies an old entry withsetCount. You've built the toy version of DevTools time travel, and it works precisely because UI = f(state).
Recap
- UI = f(state): the UI is the output of evaluating your components at a given state.
fis pure: same state in, same description out, no side effects.- "Re-render" = React calls your function again with newer state. Nothing more mystical than that.
- Predictability, testability, and time travel are all corollaries of the pure-function framing.
- Recalculating descriptions is cheap; DOM writes are expensive. React maximizes the first to minimize the second, the diff lives in Part 2.