Why React: The Mental Model

Declarative UI, the component tree, and what React actually does for you.

What you'll learn

  • The difference between describing steps and describing a result
  • Why "UI is a function of state" is the whole idea
  • What a component is, and how components form a tree
  • What React does when your data changes

The problem with updating the DOM by hand

Without a framework, you keep the page in sync with your data yourself. Every change means finding the right nodes and editing them:

let count = 0;

const button = document.querySelector("#increment");
const label = document.querySelector("#label");
const warning = document.querySelector("#warning");

button.addEventListener("click", () => {
  count = count + 1;
  label.textContent = count + " clicks"; // remember to update this
  warning.hidden = count < 5; // ...and this
});

Nothing here is difficult. The problem is that every new piece of UI adds another line you must remember to update, in every place the data changes. Miss one and the screen disagrees with the data — and that inconsistency, not the typing, is the real cost.

Describing the result instead

React inverts it. You do not write the steps to update the screen; you write what the screen should look like for the current data, and React works out the steps.

Click the button in the preview — nothing below reads or writes the DOM:

import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div style={{ fontFamily: "sans-serif", padding: 16 }}>
      <button onClick={() => setCount(count + 1)}>Add one</button>
      <p>{count} clicks</p>
      {count >= 5 && <p style={{ color: "crimson" }}>That is plenty.</p>}
    </div>
  );
}

The warning appears at five clicks, and no line of code turns it on. It is described as part of what the UI is when count >= 5. There is no separate instruction to keep in sync, because there is no separate instruction.

UI is a function of state

That is the sentence to hold on to:

view = f(state)

Give React the same data twice and you get the same screen twice. To change the screen, you change the data — never the DOM. Everything else in this course is detail on top of that one idea.

It also means you can reason about a screen by asking two questions: what data does it have, and what does it render for that data. You do not have to replay the sequence of clicks that got the user there.

Components

A component is a function that takes data and returns a description of UI. That is the entire definition. Because they are just functions, you compose them the way you compose functions:

function Greeting({ name }) {
  return <p>Hello, {name}</p>;
}

function Card({ title, children }) {
  return (
    <section style={{ border: "1px solid #ccc", borderRadius: 8, padding: 12 }}>
      <h2 style={{ margin: "0 0 8px", fontSize: 16 }}>{title}</h2>
      {children}
    </section>
  );
}

export default function App() {
  return (
    <div style={{ fontFamily: "sans-serif", padding: 16 }}>
      <Card title="Team">
        <Greeting name="Ada" />
        <Greeting name="Grace" />
      </Card>
    </div>
  );
}

App renders Card, which renders two Greetings. That nesting is the component tree, and it is how React thinks about your page: not as a document to patch, but as a tree to compare.

What React does on a change

When state changes, React:

  1. Re-runs the component to get a new description of the UI.
  2. Compares it with the previous description.
  3. Applies the difference to the real DOM — only what actually changed.

That third step is why "re-render everything" is not as expensive as it sounds. Re-rendering means calling your function again to produce a lightweight description, not rebuilding the page. In the counter above, only the text node changes; the button is never touched.

This is also why the rules you met in the refresher matter. React compares by identity, so if you mutate an object instead of copying it, step 2 sees the same object and concludes nothing changed. That is the single most common reason a component "does not update".

Common mistakes

  • Reaching for the DOM. If you find yourself calling document.querySelector in a component, the state model is usually wrong.
  • Thinking a re-render is a repaint. It is a function call producing a description; the DOM update is only the difference.
  • Mutating state. React compares by identity — change the data by replacing it, as in Destructuring, Spread, and Rest.

Exercise

The component below always shows "Signed out". Give it a useState boolean and a button that flips it, so the text follows the state. You only need to change what is rendered — never the DOM.

import { useState } from "react";

export default function App() {
  const isSignedIn = false;

  return (
    <div style={{ fontFamily: "sans-serif", padding: 16 }}>
      <p>{isSignedIn ? "Signed in" : "Signed out"}</p>
      <button>Toggle</button>
    </div>
  );
}