Free lesson · React Deep Dive
UI as a Function of State: The Core Idea
UI as a Function of State: The Core Idea
I said in Module 1 that "UI is a function of state" is the most important sentence in the course. I meant it. This lesson is where that sentence stops being philosophical and starts being operational.
The difference matters. The philosophical reading is "React is declarative; cool, got it." The operational reading is "I can predict, for any state, exactly what my UI will look like; I can predict, for any user interaction, exactly what state will change; I can predict, after the state changes, exactly what React will do." That second reading is what makes you productive in React. The first one makes you nod along to talks.
This lesson covers the loop in detail. The diagram you're about to see is the most important picture in the course. Every other lesson assumes you can see this loop in your head when you read React code.
The Loop
The mechanical version of "UI is a function of state" is a continuous loop:
flowchart TB
State["1. State<br/>(your data: count = 0,<br/>user = {...}, etc.)"]
Render["2. Render<br/>(component function runs;<br/>returns a description of UI)"]
DOM["3. DOM<br/>(React makes the actual<br/>DOM match the description)"]
Event["4. Event<br/>(user clicks, types,<br/>navigates, etc.)"]
Update["5. State update<br/>(setState; useState's updater)"]
State --> Render
Render --> DOM
DOM --> Event
Event --> Update
Update --> State
style State fill:#a78bfa,stroke:#8b5cf6,color:#000
style Render fill:#34d399,stroke:#10b981,color:#000
style DOM fill:#fbbf24,stroke:#f59e0b,color:#000
A diagram of React's core loop. Five phases connected in a cycle. State - your application's data, things like a count number, a user object, a list of items. From state, React enters the render phase, where your component function runs and returns a description of what the UI should look like for this state. From the render output, React enters the DOM phase, where it makes the actual browser DOM match the description - adding nodes, updating attributes, removing what's no longer needed. Then the application sits idle, with the DOM displayed and event listeners attached. When the user interacts - clicks, types, scrolls - an event fires. The event handler calls a state update, like setCount of a new value. The state changes, and the loop runs again from the top - render, DOM update, idle, event, update, repeat. The whole cycle is what React is. Every interaction in a React app is one trip around this loop.
Five phases. The whole framework, in one picture.
Read the diagram once more, slowly, before continuing. I want you to be able to see this loop the next time you look at any React component.
Phase 1: State
State is the data your application keeps in memory. The current count of a counter. The list of items in a todo list. The current user's profile. The text in the input field they're typing into. Whether a modal is open. The fetched data from your API.
State is the source of truth in a React application. When you want to know what the UI should look like at any moment, you look at the state. The UI is derived from it.
A key point: state lives in JavaScript, not in the DOM. In jQuery, you'd often read the DOM to figure out what the application's data is - "what's the value of #count?" In React, you'd never do that. The DOM is the output; reading from it makes as little sense as reading the screen to figure out what's in memory. The state is in useState or in a state management library or in a custom hook; the DOM displays the state.
This is one of the biggest mental shifts for engineers coming from imperative frameworks. The DOM is downstream. State is upstream. If you find yourself reaching for document.querySelector in React code, stop - almost always, the right move is to look at where the state lives.
Phase 2: Render
Render is where your component function runs.
Specifically: React calls your component function, passing in the current props. Your function reads the current state (via hooks like useState). Your function returns a description of what the UI should look like - written in JSX, which compiles to plain JavaScript objects that describe a tree of elements.
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
When this component renders, React calls Counter(). The function runs. useState(0) gives it the current count (let's say 5). The function returns a JSX tree - conceptually, something like <div><p>Count: 5</p><button>+1</button></div>. That tree is just data. It's not the DOM. It's a description of what the DOM should look like.
This is the part most beginners take a while to internalize. Render is not "draw on the screen." Render is "run your function and produce a description." Drawing happens later. The function running is fast - it's just JavaScript. The drawing is what takes time, and React minimizes that work in the next phase.
A consequence: your component function might run many times. Every state change calls it again. Every props change calls it again. The function is not the heavy work - it should be cheap to run, because React will run it often.
Phase 3: DOM
After your component returns its description of UI, React makes the actual DOM match the description.
The crucial word here is match. React doesn't throw away the existing DOM and recreate it from scratch - that would be slow. Instead, React compares the new description to the previous description and figures out the minimum set of DOM operations needed to update it.
If the previous render returned <p>Count: 5</p> and the new render returns <p>Count: 6</p>, React updates exactly one thing - the text content of that <p> element. The <div> stays. The <button> stays. The DOM doesn't get torn down and rebuilt.
This is the Virtual DOM at work, although the name is partly marketing. What React really does is keep a lightweight in-memory representation of what the DOM should look like, and diff successive versions of that representation to compute DOM updates. Module 14 covers this in detail; for now, the takeaway is: React minimizes DOM work for you.
This phase is also called the commit phase in React terminology - the part where the work computed in render gets committed to the actual DOM.
Phase 4: Event
Once the DOM is updated, the application sits idle. The user sees the UI. React isn't doing anything until something happens - a click, a keypress, a network response, a timer firing.
This "idle until something happens" model is fundamental to how React thinks. Your app isn't constantly running computation; it's reacting (the name is not accidental) to events. Between events, React is doing nothing. CPU usage is zero (well, near-zero). Power consumption is minimal. The runtime is event-driven from top to bottom.
When an event fires - say, the user clicks the +1 button - the event handler runs. The handler is the function you provided as onClick. In the counter example, that's () => setCount(count + 1).
The event handler can do whatever JavaScript it wants. It can perform calculations. It can make network requests. It can read from any source. The only thing it does to influence the UI is call a state update function.
Phase 5: State Update
When you call setCount(count + 1) (or setState, or any state-update function), React doesn't update the state immediately. It schedules an update. It says, internally: "okay, the next time I render this component, it should have count = 6."
Then React goes back to phase 1 - the state is now different, so the loop runs again. The render function runs with the new state, produces a new description, gets diffed against the previous one, updates the DOM minimally.
A subtle but important point: state updates trigger re-renders, but the component function reading useState always returns the current state for that render. You can't read the new state immediately after calling setCount. The new state shows up in the next render. We'll cover this in detail in Module 5; for now, just know that state updates are scheduled, not immediate.
What This Means for How You Write React
The loop has consequences for how you should think when writing components.
Think about what state is, before how it changes. When you start a new component, the first question is: what data does this need to remember? That data is the state. Once you have it, the render is just a function of it.
Avoid imperative DOM operations. Don't reach for document.getElementById or ref.current.style.color = 'red'. Almost always, you express what you want as state, and write the render to display it. "When the user clicks, change the color to red" becomes "when the user clicks, set color to 'red' in state; render the element with style={{ color }}." The mental shift takes a week. After that, the imperative approach feels primitive.
Don't fight the re-render. Engineers coming from imperative frameworks often try to "prevent re-renders" out of habit. In React, re-renders are cheap (most of the time) and they're how the system works. Trying to minimize them by hand is usually counterproductive. Module 17 covers when memoization actually helps - until then, write straightforward components and trust the runtime.
Trace state through the diagram in your head. When you read a component or write one, mentally walk through the loop. Where does the state live? What renders when it changes? What events trigger updates? The components you'll write are small enough that this mental trace takes seconds; doing it consistently catches a lot of bugs.
A Worked Example
Let me walk through one full loop concretely. Take the counter component:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Initial render:
- State:
count = 0. - Render: React calls
Counter(). The function returns a description of<div><p>Count: 0</p><button>+1</button></div>. - DOM: React creates the actual DOM nodes (since this is the first render, there's nothing to diff against). The user sees "Count: 0" and a "+1" button.
- Idle. React sits and waits.
User clicks the button:
- Event: The browser fires a click event on the button. React's synthetic event system catches it and calls the handler -
() => setCount(count + 1). At this moment,countis still 0 (the value from the current render's closure), so the handler callssetCount(1). - State update: React schedules a state update. Internally: "next render of Counter has
count = 1." - State (next iteration):
count = 1. - Render: React calls
Counter()again. The function returns a description of<div><p>Count: 1</p><button>+1</button></div>. - DOM: React diffs against the previous description. The only difference is the text "0" → "1" inside the
<p>. React updates exactly that text node. The<div>and<button>aren't touched. - Idle. Back to waiting for the next event.
That's the whole interaction. Five phases, one trip. Predict every line of it before running the code; you can.
Why This Matters Going Forward
Every advanced React topic is an elaboration of this loop.
Hooks (Modules 5-12) are how you express state and side effects within the loop.
Reconciliation (Module 14) is the algorithm for phase 3 - how React diffs the descriptions.
Performance (Modules 16-17) is about making the loop fast - avoiding unnecessary renders, optimizing expensive renders, batching state updates.
Effects (Module 6) are how you do things that aren't part of the render → DOM pipeline - like network requests, subscriptions, browser API calls.
Server Components (Module 25) are a refinement of where the render phase runs - some components render on the server, never running in the browser at all.
If you understand the loop, every advanced topic has a clear place to slot into. If you don't, the advanced topics will feel like a list of disconnected tricks. So please, hold the loop in your head. Sketch the diagram on paper if it helps. Make it muscle memory.
What I Want You to Take From This Lesson
Six things.
One: The loop has five phases. State, render, DOM, event, state update. Repeat.
Two: State is the source of truth. Not the DOM. State lives in JavaScript; DOM is the display.
Three: Render means your function runs. It doesn't mean "draw on screen." Drawing is the next phase.
Four: React minimizes DOM work for you. You write what the UI should look like; React figures out the minimum updates.
Five: Events trigger state updates; state updates schedule re-renders. Updates are not immediate - the new state shows up in the next render.
Six: Hold this loop in your head whenever you read or write React. Every advanced topic is an elaboration of it.
Try It
Exercise: Warmup (15 minutes). Draw the five-phase loop from memory on paper. Don't look at the diagram. Then compare. Note what you missed.
Exercise: Core (20 minutes). Take the counter example. Trace every line of one full click-to-update interaction. Write out, in prose, what happens at each phase. Specifically describe what
countequals at each moment.
Exercise: Core (15 minutes). Read React docs - "Reacting to Input with State". Note how the docs framing relates to the loop in this lesson.
Exercise: Stretch (30 minutes). Sketch (in prose, no code yet) a slightly bigger UI - a todo list with add and delete. What state would it have? What renders would happen when an item is added? What about when deleted? Walk through the loop for each.