# React Fundamentals: Components, JSX, State, and Re-rendering

### Why React Exists?

Let's start with a question. **Why did developers create React when JavaScript already existed?**

Good question right? It's worth separating "JavaScript" (the language) from "how people were using it to build UIs" (the practice), because React didn't replace JavaScript, it was built to fix problems in how JavaScript was being used to manipulate the DOM.

**JavaScript gave you the tools, not the structure**

JavaScript has always been able to manipulate the DOM — that was never the issue. The problem was that JavaScript (and libraries like jQuery) gave developers **low-level tools with no opinion on how to organize UI logic** as apps grew.

With plain JavaScript, you were responsible for:

*   Finding the exact DOM node to update (`document.getElementById`, etc.)
    
*   Manually changing it when data changed
    
*   Remembering every other place on the page that depended on that same data and updating those too
    

This works fine for a simple page. It breaks down fast for something like Facebook's newsfeed, where a single like, comment, or notification might need to update five different parts of the screen at once.

### The specific pain point: manual synchronization

This was the actual problem React was invented to solve, at Facebook, around 2011:

*   Facebook's notification counter would get out of sync with the actual number of notifications.
    
*   Engineers would fix it in one place, and it would break somewhere else, because the "state" of the UI lived in scattered DOM elements and callback functions, not in one predictable place.
    
*   As the codebase grew, nobody could confidently say "if I change this data, here's everything that updates" — because that logic was hand-wired, imperatively, all over the code.
    

This is sometimes called **"callback hell"** or **spaghetti state** — not a JavaScript limitation exactly, but a limitation of *imperative DOM manipulation* as an approach, once an app crosses a certain complexity threshold.

### What React actually added?

React didn't give you new JavaScript capabilities. It gave you a **pattern and a runtime** on top of JavaScript:

| Without React | With React |
| --- | --- |
| You manually update the DOM when data changes | You update *data (state)*, React figures out what DOM changes are needed |
| UI logic scattered across event handlers | UI logic organized into components |
| No single source of truth | State lives in one predictable place per component |
| You write imperative steps ("find this element, change its text") | You write declarative descriptions ("this is what the UI should look like given this data") |

So the honest framing is: **JavaScript was the language; DOM manipulation via that language was the fragile practice.** React was created not because JavaScript was insufficient as a language, but because there was no good *architecture* for building UIs that update frequently and predictably at scale — and jQuery-style direct manipulation didn't scale past a certain size of application.

It's the same reason frameworks exist in general: the language is capable, but doesn't enforce good structure on its own.

So the question arises. What is React?

### What is React?

React.js is a front-end JavaScript library for building interactive user interfaces. It was developed by Facebook and is maintained by Facebook and the open-source community. React.js is a phenomenal library that is easy to understand, has excellent cross-platform support, has a fantastic community, and is one of the most loved libraries out there.

![](https://cdn.hashnode.com/uploads/covers/67860697e7c9394b88aed37e/d987b2e1-05e0-493f-b6b8-5a8e87ecc9ae.png align="center")

By user interfaces (UI), we mean the elements that users see and interact with on-screen. By library, we mean React provides helpful functions (APIs) to build UI, but leaves it up to the developer where to use those functions in their application.

### **Understanding JSX**

JSX (**JavaScript XML**) is a syntax extension for JavaScript that lets you write HTML-like markup directly inside your JavaScript code.

```xml
const element = <h1>Hello, world!</h1>;
```

This isn't valid plain JavaScript or HTML, it's a special syntax that gets transformed before the browser ever sees it. JSX isn't required to use React, but it's the standard way most React developers write UI code because it closely mirrors the final rendered output.

You would ask that why was JSX introduced if we can write markup using HTML?

Before JSX, building UI in JavaScript looked like this:

```javascript
React.createElement('h1', { className: 'title' }, 'Hello, world!');
```

This works, but becomes unreadable fast once you nest multiple elements:

```javascript
React.createElement('div', null,
  React.createElement('h1', null, 'Hello'),
  React.createElement('p', null, 'Welcome to my app')
);
```

React's core idea is that **markup and the logic that produces it are tightly coupled** a button's click handler, its label, and its styling all describe the *same* piece of UI, so separating them into different files (as with traditional HTML/CSS/JS separation) often made code harder to follow, not easier.

JSX was introduced to:

*   Let developers write UI structure in a familiar, HTML-like way
    
*   Keep markup and logic together in one place, colocated by component rather than by file type
    
*   Make code easier to visualize — you can look at JSX and get a rough mental picture of the rendered output
    
*   Provide compile-time checks and better tooling (autocomplete, syntax errors) compared to using strings or nested function calls
    

### JSX vs HTML

They look similar but aren't the same thing. Key differences:

| HTML | JSX |
| --- | --- |
| `class="btn"` | `className="btn"` (since `class` is a reserved word in JS) |
| `for="name"` | `htmlFor="name"` |
| `onclick="fn()"` | `onClick={fn}` (camelCase, references a function) |
| Self-closing optional (`<img>`) | Self-closing required (`<img />`) |
| Attributes are strings | Attributes can be any JS expression (`disabled={isLoading}`) |
| Standalone document | Must return a **single root element** (or a Fragment `<>...</>`) |
| Case-insensitive tags | Case-sensitive — lowercase = HTML tag, Capitalized = React component |

### Embedding JavaScript Inside JSX

You embed JavaScript expressions using curly braces {}:

```javascript
  function Greeting({ name, isLoggedIn }) {
  const currentHour = new Date().getHours();

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>The time is {currentHour}:00</p>

      {/* Conditional rendering */}
      {isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}

      {/* Rendering a list */}
      <ul>
        {['Apple', 'Banana', 'Cherry'].map(fruit => (
          <li key={fruit}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}
```

Rules to keep in mind:

*   Curly braces accept **expressions**, not statements — `{if (x) {...}}` is invalid, but `{x ? a : b}` works.
    
*   You can embed variables, function calls, ternaries, and array `.map()` results — but not `for` loops or `if` statements directly.
    
*   `{}` used for JS expressions is different from `{{}}` you'll see for inline styles — that's actually a JS object literal passed inside a single pair of braces: `style={{ color: 'red' }}`.
    

Browsers don't understand JSX natively — it must be **compiled/transpiled** into regular JavaScript before it runs. This is typically done by **Babel** (or a bundler like Vite/esbuild using similar tooling).

**Step by step:**

**1\. You write JSX:**

```xml
   const element = <h1 className="title">Hello</h1>;
```

**2\. Babel transforms it into a function call.** In modern React (17+), this uses the automatic JSX runtime:

```javascript
   import { jsx as _jsx } from "react/jsx-runtime";
   const element = _jsx("h1", { className: "title", children: "Hello" });
```

(Older versions compiled to `React.createElement("h1", { className: "title" }, "Hello")` — functionally equivalent.)

**3\. That function call returns a plain JavaScript object** — a lightweight description of the UI, often called a **React element**:

```javascript
   {
     type: "h1",
     props: { className: "title", children: "Hello" }
   }
```

**4\. React uses this object tree (the "virtual DOM")** to figure out what actual DOM nodes need to be created or updated, then applies only the necessary changes to the real DOM.

So the pipeline is:

```plaintext
JSX  →  Babel/compiler  →  JS function calls  →  React elements (plain objects)  →  Real DOM updates
```

### **Components in React**

User interfaces can be broken down into smaller building blocks called **components**.

Components allow you to build self-contained, reusable snippets of code. If you think of components as **LEGO bricks**, you can take these individual bricks and combine them together to form larger structures. If you need to update a piece of the UI, you can update the specific component or brick.

![](https://cdn.hashnode.com/uploads/covers/67860697e7c9394b88aed37e/6379eed8-afe2-4960-af2a-0c981fd47ac1.png align="center")

### Breaking Large UIs into Smaller Pieces

As UIs grow, it becomes hard to manage everything in one giant component. The standard practice is to **decompose** a large UI into smaller, focused components — each responsible for one clear job.

For example, a full page might break down like this:

```javascript
function ProfilePage() {
  return (
    <div>
      <Header />
      <ProfileInfo />
      <PostList />
      <Footer />
    </div>
  );
}
```

Each of `Header`, `ProfileInfo`, `PostList`, and `Footer` is its own component, possibly composed of even smaller components (e.g., `PostList` might render many `Post` components).

**Benefits of breaking things down this way:**

*   **Readability** — each component is small and easy to understand
    
*   **Reusability** — a `Button` or `Card` component can be used all over the app
    
*   **Testability** — small components are easier to test in isolation
    
*   **Maintainability** — changes to one piece (e.g., how a `Post` looks) don't require touching unrelated code
    

### Props in React

To make our components accept different data, we can use props. Props are arguments passed into React components. They are passed to components via HTML attributes.

Props is just a shorter way of saying properties.

We use props in React to pass data from one component to another (from a parent component to child components), But you can't pass props from a child component to parent components. Data from props is read-only and cannot be modified by a component receiving it from outside.

Props flow **one way**: from parent to child. A parent component decides what data a child needs and passes it down explicitly.

```javascript
function App() {
  const user = { name: "Ananya", role: "Admin" };

  return <UserProfile user={user} />;
}

function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.role}</p>
    </div>
  );
}
```

Here, `App` is the parent and `UserProfile` is the child. The `user` object is created in `App` and handed down as a prop.

Since data only flows downward, how does a child "talk back" to a parent? The common pattern is to pass a **function** as a prop. The child calls that function, and the parent handles the logic.

```javascript
function App() {
  const handleButtonClick = (message) => {
    console.log("Child said:", message);
  };

  return <Child onNotify={handleButtonClick} />;
}

function Child({ onNotify }) {
  return (
    <button onClick={() => onNotify("Button was clicked!")}>
      Click me
    </button>
  );
}
```

**Key takeaway:** the same component, combined with different props, can render endless variations, that's the core idea that makes React components powerful and reusable across an entire application.

### State in React

A State is a plain JavaScript object used by React to represent a piece of information about the component's current situation. It's managed in the component (just like any variable declared in a function).

The state object is where you store property values that belongs to the component. When the state object changes, the component re-renders. State data can be modified by its own component, but is private (cannot be accessed from outside)

```javascript
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return <p>Current count: {count}</p>;
}
```

Here, `count` is a piece of state. `useState(0)` initializes it to `0`, and `setCount` is the function used to update it.

Without state, components could only render static output based on props, they'd have no way to "remember" anything or react to user interaction (clicks, typing, toggles, etc.).

State exists to answer the question: **"What does this component need to remember between renders?"**

Examples of things that typically need state:

*   Whether a modal is open or closed
    
*   The current value of an input field
    
*   Items in a shopping cart
    
*   Whether data is still loading
    
*   The active tab in a tab bar
    

Without state, none of these interactive behaviors would be possible — the UI would just be a fixed snapshot.

You never modify state directly. Instead, you call the **setter function** returned by `useState`, and React handles re-rendering the component with the new value.

```javascript
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1); // ✅ correct
  // count = count + 1; // ❌ never do this directly

  return <button onClick={increment}>Count: {count}</button>;
}
```

**Important nuance — state updates can be asynchronous/batched.** If a new state value depends on the previous one, use the **functional update** form to avoid stale values:

```javascript
function Counter() {
  const [count, setCount] = useState(0);

  const incrementTwice = () => {
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
  };

  return <button onClick={incrementTwice}>Count: {count}</button>;
}
```

Using `(prev) => prev + 1` guarantees each update builds on the latest value, rather than relying on a `count` variable that might be stale within the same render.

Also, calling a setter **triggers a re-render** — React re-runs the component function with the updated state so the UI reflects the change.

### Understanding Re-Rendering

A **re-render** happens when React calls a component function again to figure out what the UI should look like now. There are three main triggers:

1.  **State changes** — calling a `useState` setter function
    
2.  **Props changes** — a parent re-renders and passes new prop values down
    
3.  **Parent re-renders** — when a parent component re-renders, its children usually re-render too (even if their own props didn't change)
    

```javascript
function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Display count={count} />
    </div>
  );
}

function Display({ count }) {
  console.log("Display rendered");
  return <p>Count: {count}</p>;
}
```

Every time the button is clicked, `setCount` runs → `App` re-renders → `Display` receives a new `count` prop → `Display` re-renders too.

When you call a state setter, React schedules a re-render of that component (and its children). The component function runs again from top to bottom, producing new JSX based on the current state values.

```javascript
function Toggle() {
  const [isOn, setIsOn] = useState(false);

  console.log("Toggle rendered, isOn =", isOn);

  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? "ON" : "OFF"}
    </button>
  );
}
```

Each click flips `isOn`, which triggers a re-render, which prints a new log line and updates the button label. React compares the new JSX output to the previous one (using a process involving the **virtual DOM**) and only updates the real DOM where something actually changed — it doesn't repaint the whole page.

**Note:** calling a setter with the *same* value it already holds (e.g., `setCount(5)` when `count` is already `5`) will generally **not** trigger a re-render — React bails out early since there's nothing new to reflect.

### Component Lifecycle from a High Level

Every component roughly goes through three phases:

1.  **Mounting** — the component is created and inserted into the DOM for the first time
    
2.  **Updating** — the component re-renders due to state or prop changes (this is the re-rendering we've been discussing)
    
3.  **Unmounting** — the component is removed from the DOM (e.g., a conditional stops rendering it)
    

```javascript
function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    console.log("Mounted!");
    const interval = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);

    return () => {
      console.log("Unmounted, cleaning up interval");
      clearInterval(interval);
    };
  }, []);

  return <p>Seconds: {seconds}</p>;
}
```

*   The `useEffect` with an empty `[]` dependency array runs once on **mount**.
    
*   Its return function (the cleanup) runs on **unmount**.
    
*   Every time `seconds` changes, that's an **update** (re-render) — but since `[]` is empty, the effect itself doesn't re-run on every update, only the cleanup/setup pair runs once.
    

This mount → update (many times) → unmount cycle is the high-level lifecycle every component goes through.

### Why React Updates the UI Automatically

This goes back to React's core philosophy: **the UI is a function of state and props.** You never manually tell React "change this text" or "hide this button." Instead:

1.  You describe what the UI *should* look like for any given state/props (via JSX)
    
2.  When state or props change, React re-runs that description
    
3.  React compares the new description to the old one (virtual DOM diffing)
    
4.  React updates only the parts of the real DOM that actually changed (this process is often called **reconciliation**)
    

```javascript
// You just describe the "what":
return isLoggedIn ? <Dashboard /> : <LoginPage />;

// You never write imperative code like:
// document.getElementById("app").innerHTML = isLoggedIn ? dashboardHTML : loginHTML;
```

This automation is why React feels declarative and predictable — you don't have to manually track *which* DOM elements need updating when data changes. You just update the state, and React figures out the rest efficiently.

### Imperative vs Declarative Programming

**Imperative programming** means writing step-by-step instructions telling the computer *how* to do something — you manually manage each action and mutation.

**Declarative programming** means describing *what* you want the end result to be, and letting something else (React, in this case) figure out how to achieve it.

**Imperative example (vanilla JS/DOM manipulation):**

```javascript
// Imperative: manually walk through every step
const button = document.createElement("button");
button.textContent = "Off";
button.className = "btn btn-off";

button.addEventListener("click", () => {
  if (button.textContent === "Off") {
    button.textContent = "On";
    button.className = "btn btn-on";
  } else {
    button.textContent = "Off";
    button.className = "btn btn-off";
  }
});

document.body.appendChild(button);
```

Here, you're responsible for every mutation: creating the element, setting its text, updating its class, tracking its current state by reading the DOM itself.

**Declarative example (React):**

```javascript
function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button
      className={isOn ? "btn btn-on" : "btn btn-off"}
      onClick={() => setIsOn(!isOn)}
    >
      {isOn ? "On" : "Off"}
    </button>
  );
}
```

Here, you just describe: *"Given* `isOn`*, the button should look like this."* You never touch the DOM directly or track what the button currently says — React handles that.

### Describing UI Instead of Manipulating UI

In the imperative world, you think in terms of commands: *"find this element, change its text, add this class, remove that class."* Bugs often creep in because the actual DOM state and your mental model of it can drift out of sync — especially as an app grows.

In React, you never issue direct manipulation commands. Instead, you write a function that maps data → UI:

```javascript
function ShoppingCartBadge({ itemCount }) {
  return (
    <span className="badge">
      {itemCount > 0 ? itemCount : ""}
    </span>
  );
}
```

### Benefits of Declarative Rendering

1\. Less manual bookkeeping You don't need to track the DOM's current state yourself — the component's state/props are the source of truth.

2\. Easier to reason about Given a specific state, the output is always the same. You can look at a component and know exactly what will render, without tracing through a sequence of mutations.

```javascript
function StatusBadge({ status }) {
  if (status === "success") return <span className="badge green">✓ Success</span>;
  if (status === "error") return <span className="badge red">✕ Error</span>;
  return <span className="badge gray">Pending</span>;
}
```

No matter how status got to its current value, this always renders the same way — no hidden mutation history to worry about.

3\. Fewer bugs from inconsistent UI state In imperative code, it's easy to forget to update one part of the UI when the underlying data changes (e.g., updating the count but forgetting to update the badge's visibility). Declarative rendering ties everything to the same source of truth, so nothing gets left behind.

4\. Composability Declarative components describe themselves purely in terms of inputs, so they can be nested, reused, and combined without worrying about when or how they get updated — that's handled uniformly by the framework.

### Component Tree Architecture

**1\. Parent Components**

A **parent component** is any component that renders another component inside it. Parents are responsible for:

*   Deciding *what* children to render
    
*   Passing data down to children (via props)
    
*   Coordinating shared state that multiple children need
    

```javascript
function App() {
  return (
    <div>
      <Header />
      <Dashboard />
    </div>
  );
}
```

Here, `App` is the parent of `Header` and `Dashboard`. It controls their placement and can pass them whatever data they need.

**2\. Child Components**

A **child component** is any component rendered inside another. Children receive data from their parent via props and typically don't know (or care) who their parent is — they just render based on whatever props they're given.

```javascript
function Dashboard() {
  const user = { name: "Kavya", role: "Editor" };

  return <ProfileCard user={user} />;
}

function ProfileCard({ user }) {
  return (
    <div className="card">
      <h3>{user.name}</h3>
      <p>{user.role}</p>
    </div>
  );
}
```

`ProfileCard` is a child of `Dashboard`. It has no idea *where* `user` came from — it just renders whatever it's handed. This decoupling is what makes components reusable in different parts of a tree.

A component can be a parent *and* a child at the same time — `Dashboard` is a child of `App`, but a parent to `ProfileCard`.

**3\. Data Flow**

React data flow is **unidirectional** (one-way): data flows *down* the tree via props, from parent to child, to grandchild, and so on.

```javascript
function App() {
  const currentUser = { name: "Ishaan", isAdmin: true };

  return <Dashboard user={currentUser} />;
}

function Dashboard({ user }) {
  return <Sidebar user={user} />;
}

function Sidebar({ user }) {
  return <UserBadge user={user} />;
}

function UserBadge({ user }) {
  return <p>{user.name} {user.isAdmin && "(Admin)"}</p>;
}
```

Here, `currentUser` starts in `App` and flows down through three layers (`Dashboard` → `Sidebar` → `UserBadge`) before it's finally used. This pattern — passing a prop through components that don't use it themselves, just to get it to a deeply nested child — is sometimes called **"prop drilling."**

Prop drilling works fine for shallow trees, but in deeply nested apps it can get unwieldy. That's usually solved later with tools like **Context** or state management libraries — but the underlying data flow rule (parent → child) never changes.

If a deeply nested child needs to send data back *up*, it does so the same way covered in the props lesson: the parent passes a **callback function** down as a prop, and the child calls it.

**4\. Application Structure**

As real apps grow, component trees are typically organized around a few common patterns:

1.  Layout components — structural pieces that stay consistent across pages (Header, Footer, Sidebar, Layout)
    
2.  Page/container components — represent a full "screen" or route, often responsible for fetching data and coordinating state
    
3.  Presentational/UI components — small, reusable, often stateless pieces focused purely on rendering (Button, Card, Avatar, Badge)
    

```javascript
function HomePage() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetchPosts().then(setPosts);
  }, []);

  return (
    <Layout>
      <PostList posts={posts} />
    </Layout>
  );
}

function Layout({ children }) {
  return (
    <div>
      <Header />
      <main>{children}</main>
      <Footer />
    </div>
  );
}
```

Here, `HomePage` acts as a **container** (handles data/state), `Layout` provides consistent **structure**, and smaller components like `PostList` and `Post` handle **presentation**.

**Where should state live?**  
A useful rule of thumb: state should live in the **lowest common ancestor** of all the components that need it. If two sibling components both need access to the same piece of data, lift that state up to their shared parent rather than duplicating it.

### React Rendering Lifecycle Overview

**The Three Phases**

Every component render in React goes through three high-level phases: **Trigger**, **Render**, and **Commit**. Understanding these separately clears up a lot of confusion about *when* things actually happen.

```plaintext
Trigger → Render → Commit
```

**Phase 1: Trigger**

A render is *triggered* by one of two things:

1.  **Initial mount** — the component is rendered for the first time (e.g., `ReactDOM.createRoot(...).render(<App />)`)
    
2.  **State/props update** — a `useState` setter is called, a parent re-renders, or context changes
    

```javascript
// Trigger #1: Initial mount
ReactDOM.createRoot(document.getElementById("root")).render(<App />);

// Trigger #2: State update
function App() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
  // Clicking the button "triggers" a re-render
}
```

**Phase 2: Render**

During the render phase, React calls your component functions to figure out what the UI *should* look like. This phase is **pure calculation** — React builds a description of the UI (the virtual DOM / React element tree) but does **not** touch the actual browser DOM yet.

```javascript
function Greeting({ name }) {
  console.log("Rendering Greeting"); // happens during the render phase
  return <h1>Hello, {name}</h1>;
}
```

Key facts about the render phase:

*   It must be **pure** — no side effects (no DOM mutation, no network calls) should happen directly in the component body
    
*   On the **initial render**, React calls every component in the tree
    
*   On a **re-render**, React calls only the components that were triggered to update (and, by default, their children — as covered in the re-rendering lesson)
    
*   React can pause, abort, or restart this phase (especially with concurrent features) since it's just calculation, not real changes yet
    

```javascript
function Timer() {
  // ❌ Side effect during render — not allowed
  document.title = "Timer running";

  return <p>Time's up!</p>;
}
```

```javascript
function Timer() {
  // ✅ Side effect moved to useEffect, runs after render/commit
  useEffect(() => {
    document.title = "Timer running";
  });

  return <p>Time's up!</p>;
}
```

**Phase 3: Commit**

Once React finishes calculating the new UI description, it moves to the **commit phase** — this is where React actually applies changes to the real DOM.

*   On **initial mount**, React uses `appendChild()` (or similar) to insert all the DOM nodes it created
    
*   On a **re-render**, React compares the new element tree to the previous one (a process called **reconciliation** / "diffing") and applies only the **minimal set of changes** needed
    

```javascript
function Clock({ time }) {
  return (
    <div>
      <h1>Current time</h1>
      <p>{time}</p>
    </div>
  );
}
```

If `time` changes, React doesn't recreate the `<h1>` or the `<div>` — it diffs the tree, sees only the text inside `<p>` changed, and updates *just that text node* in the real DOM.

After the commit phase, the browser paints the updated screen, and any `useEffect` callbacks (that had a dependency change) run.

### Putting It Together: A Full Cycle

```javascript
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Committed! count is now:", count);
  }, [count]);

  console.log("Rendering with count =", count);

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
```

Clicking the button produces this sequence:

1.  **Trigger** — `setCount(count + 1)` is called
    
2.  **Render** — `Counter` function runs again, logs `"Rendering with count = 1"`, produces new JSX
    
3.  **Commit** — React updates the button's text in the real DOM
    
4.  **Paint** — browser shows the updated button
    
5.  **Effect** — `useEffect` runs (since `count` changed), logs `"Committed! count is now: 1"`
    

### Mount vs. Update vs. Unmount (Lifecycle Stages)

Zooming out, this Trigger → Render → Commit cycle repeats across a component's lifetime, which breaks into three stages:

| Stage | What happens | Hook equivalent |
| --- | --- | --- |
| **Mount** | Component is created and inserted into the DOM for the first time | `useEffect(() => {...}, [])` |
| **Update** | Component re-renders due to state/props/context changes | `useEffect(() => {...}, [dep])` |
| **Unmount** | Component is removed from the DOM | Cleanup function returned from `useEffect` |

```javascript
function ChatRoom({ roomId }) {
  useEffect(() => {
    console.log(`Mounted / roomId changed to ${roomId}`);
    const connection = connectToRoom(roomId);

    return () => {
      console.log(`Cleaning up before unmount / before next roomId`);
      connection.disconnect();
    };
  }, [roomId]);

  return <p>Connected to {roomId}</p>;
}
```

If `roomId` changes, React runs the **cleanup** from the previous render before running the **new effect** — this is sometimes called "unmount + remount" behavior for effects, even though the component itself doesn't actually unmount from the DOM.
