Episode 2 — React Frontend Architecture NextJS / 2.1 — Introduction to React

2.1.a — Why React Exists

In one sentence: React was created by Facebook to solve the pain of building large-scale, data-driven user interfaces where manual DOM manipulation becomes unmanageable, error-prone, and slow.

Navigation: ← Overview · Next → Declarative vs Imperative UI


Table of Contents

  1. The Problem That Started It All
  2. Manual DOM Manipulation at Scale
  3. The jQuery Era — What Went Wrong
  4. Facebook's Specific Pain Point
  5. The Birth of React — 2013
  6. What React Actually Is
  7. React's Core Philosophy
  8. How React Solves the DOM Problem
  9. React vs Vanilla JavaScript — A Real Comparison
  10. Why Not Just Use Templates?
  11. React's Ecosystem and Popularity
  12. When React Is NOT the Right Choice
  13. React's Evolution — From Classes to Hooks
  14. The Mental Model Shift
  15. Key Takeaways

1. The Problem That Started It All

Before we write a single line of React code, we need to understand why it exists. Every technology is born from a problem. If you don't understand the problem, you'll never truly understand the solution.

The Early Web (1995–2005)

In the beginning, websites were static HTML documents. A server generated the full HTML page, sent it to the browser, and the browser displayed it. If you wanted to change anything on the page — even a single character — the entire page had to reload.

┌──────────┐    Request     ┌──────────┐
│          │ ─────────────▶ │          │
│ Browser  │                │  Server  │
│          │ ◀───────────── │          │
└──────────┘  Full HTML     └──────────┘
                 Page
     User clicks a link → FULL PAGE RELOAD

This worked fine for simple websites — news articles, personal homepages, online encyclopedias. But as the web grew more interactive, this model broke down.

The Rise of Dynamic Interfaces

By the mid-2000s, web applications were becoming more ambitious:

  • Gmail (2004) — real-time email updates without page reload
  • Google Maps (2005) — drag, zoom, and search without losing your position
  • Facebook (2004+) — news feed, notifications, chat, all updating in real-time

These apps needed to change parts of the page without reloading the whole thing. This is where JavaScript DOM manipulation entered the picture.

What Is the DOM?

The DOM (Document Object Model) is the browser's representation of your HTML page as a tree of objects. Every HTML element becomes a JavaScript object that you can read, modify, or delete.

HTML:
<div id="app">
  <h1>Hello</h1>
  <p>World</p>
</div>

DOM Tree:
          document
              │
           <html>
              │
           <body>
              │
          <div#app>
           /      \
       <h1>        <p>
        │           │
     "Hello"     "World"

JavaScript can manipulate this tree directly:

// Find an element
const heading = document.getElementById('app').querySelector('h1');

// Change its content
heading.textContent = 'Goodbye';

// Add a new element
const newParagraph = document.createElement('p');
newParagraph.textContent = 'New content';
document.getElementById('app').appendChild(newParagraph);

// Remove an element
const oldParagraph = document.querySelector('p');
oldParagraph.remove();

// Change styles
heading.style.color = 'red';
heading.style.fontSize = '24px';

// Add event listeners
heading.addEventListener('click', () => {
  alert('You clicked the heading!');
});

This seems simple enough. So what's the problem?


2. Manual DOM Manipulation at Scale

The problem isn't that DOM manipulation is hard to write. The problem is that it becomes impossible to manage as your application grows.

A Simple Counter — Vanilla JS

Let's build a counter with vanilla JavaScript:

<div id="counter-app">
  <h2>Count: 0</h2>
  <button id="increment">+1</button>
  <button id="decrement">-1</button>
  <button id="reset">Reset</button>
</div>

<script>
  let count = 0;
  const display = document.querySelector('#counter-app h2');
  
  document.getElementById('increment').addEventListener('click', () => {
    count++;
    display.textContent = `Count: ${count}`;
  });
  
  document.getElementById('decrement').addEventListener('click', () => {
    count--;
    display.textContent = `Count: ${count}`;
  });
  
  document.getElementById('reset').addEventListener('click', () => {
    count = 0;
    display.textContent = `Count: ${count}`;
  });
</script>

That's about 20 lines of JavaScript for a counter. Manageable. Now let's add requirements:

  • Show "Positive", "Negative", or "Zero" below the count
  • Change the color to green (positive), red (negative), or gray (zero)
  • Disable the decrement button when count is 0
  • Show a history of the last 5 changes
<div id="counter-app">
  <h2 id="count-display">Count: 0</h2>
  <p id="status" style="color: gray">Zero</p>
  <button id="increment">+1</button>
  <button id="decrement" disabled>-1</button>
  <button id="reset">Reset</button>
  <h3>History</h3>
  <ul id="history"></ul>
</div>

<script>
  let count = 0;
  let history = [];
  
  const countDisplay = document.getElementById('count-display');
  const status = document.getElementById('status');
  const decrementBtn = document.getElementById('decrement');
  const historyList = document.getElementById('history');
  
  function updateUI() {
    // Update count display
    countDisplay.textContent = `Count: ${count}`;
    
    // Update status text and color
    if (count > 0) {
      status.textContent = 'Positive';
      status.style.color = 'green';
    } else if (count < 0) {
      status.textContent = 'Negative';
      status.style.color = 'red';
    } else {
      status.textContent = 'Zero';
      status.style.color = 'gray';
    }
    
    // Update button state
    decrementBtn.disabled = count === 0;
    
    // Update history
    historyList.innerHTML = '';
    history.slice(-5).forEach(entry => {
      const li = document.createElement('li');
      li.textContent = entry;
      historyList.appendChild(li);
    });
  }
  
  document.getElementById('increment').addEventListener('click', () => {
    count++;
    history.push(`Incremented to ${count}`);
    updateUI();
  });
  
  document.getElementById('decrement').addEventListener('click', () => {
    if (count === 0) return;
    count--;
    history.push(`Decremented to ${count}`);
    updateUI();
  });
  
  document.getElementById('reset').addEventListener('click', () => {
    count = 0;
    history.push('Reset to 0');
    updateUI();
  });
  
  updateUI(); // initial render
</script>

We're now at 50+ lines for a counter. Notice the pattern:

  1. Data exists in JavaScript variables (count, history)
  2. UI exists in the DOM (countDisplay, status, historyList)
  3. Every time data changes, we manually update EVERY piece of the DOM that depends on it

This is the core problem: keeping the data (state) and the UI (DOM) in sync.

The Synchronization Nightmare

┌─────────────────────────────────────────────────────┐
│               THE MANUAL DOM PROBLEM                 │
├─────────────────────────────────────────────────────┤
│                                                       │
│   STATE (JavaScript)          DOM (Browser)           │
│   ┌─────────────┐            ┌─────────────┐         │
│   │ count = 5   │ ──sync──▶  │ "Count: 5"  │         │
│   │ status =    │ ──sync──▶  │ "Positive"  │         │
│   │  "Positive" │            │ color:green │         │
│   │ history = [ │ ──sync──▶  │ <li>...</li>│         │
│   │   ...       │            │ <li>...</li>│         │
│   │ ]           │            │ <li>...</li>│         │
│   │ disabled =  │ ──sync──▶  │ btn.disabled│         │
│   │   false     │            │  = false    │         │
│   └─────────────┘            └─────────────┘         │
│                                                       │
│   YOU are responsible for EVERY arrow.                │
│   Miss one? Bug. Wrong order? Bug. Race condition?    │
│   Bug. Memory leak from old listeners? Bug.           │
│                                                       │
└─────────────────────────────────────────────────────┘

Now imagine this at the scale of Facebook's news feed:

  • Hundreds of posts, each with like counts, comment counts, share buttons
  • Real-time notifications updating in the header
  • Chat messages coming in at the sidebar
  • Friend suggestions updating
  • Ad content rotating
  • All of these interacting with each other

Manual DOM synchronization at this scale is essentially impossible without bugs.

The Five Horsemen of Manual DOM

ProblemDescriptionExample
State-UI DesyncDOM doesn't reflect current dataLike count shows 5 but state says 6
Spaghetti UpdatesEvery data change touches multiple DOM nodesChanging user name requires updating header, sidebar, comments
Memory LeaksEvent listeners and references not cleaned upOld list items still holding references after removal
Inconsistent StateDifferent parts of UI show different statesChat shows "online" but header shows "offline"
Performance BottlenecksTouching DOM is expensive, bulk updates cause reflowsUpdating 100 list items triggers 100 layout recalculations

Why DOM Operations Are Slow

The browser DOM is not a simple JavaScript object. Each DOM operation triggers a cascade:

┌─────────────────────────────────────────────────────┐
│        WHAT HAPPENS WHEN YOU TOUCH THE DOM           │
├─────────────────────────────────────────────────────┤
│                                                       │
│  element.style.width = '200px';                       │
│                                                       │
│  1. Browser receives the change                       │
│  2. Recalculate styles  (CSS cascade)                │
│  3. Layout / Reflow     (compute positions & sizes)  │
│  4. Paint               (fill in pixels)             │
│  5. Composite           (combine layers)             │
│                                                       │
│  Steps 2-5 may cascade: one change can force         │
│  ALL sibling/child elements to recalculate too.      │
│                                                       │
│  Do this 100 times in a loop = 100 reflows.          │
│  The page visibly stutters and hangs.                │
│                                                       │
└─────────────────────────────────────────────────────┘

A single style change can trigger a "reflow" — the browser recalculates the position and size of every affected element on the page. Batch many changes together without careful management, and the browser spends more time recalculating layouts than actually displaying your content.


3. The jQuery Era — What Went Wrong

jQuery (2006) was the first major attempt to make DOM manipulation easier. It simplified the API and handled browser inconsistencies:

// Vanilla JS (pre-jQuery, cross-browser nightmares)
var el;
if (document.getElementById) {
  el = document.getElementById('myDiv');
} else if (document.all) {
  el = document.all['myDiv'];  // IE5
}
if (el.addEventListener) {
  el.addEventListener('click', handler);
} else if (el.attachEvent) {
  el.attachEvent('onclick', handler);  // IE8
}

// jQuery — one line, all browsers
$('#myDiv').click(handler);

jQuery made DOM manipulation easier to write but didn't solve the fundamental problem — you were still manually keeping state and UI in sync.

jQuery's Real Contribution

jQuery deserves credit for:

  1. Normalizing browser APIs — Write once, run on IE6, Firefox, Chrome, Safari
  2. Simplifying DOM traversal — CSS selectors to find elements
  3. AJAX made easy$.ajax() simplified network requests
  4. Plugin ecosystem — Thousands of UI components
  5. Teaching a generation — jQuery taught millions of developers JavaScript

Where jQuery Failed at Scale

// A real-world jQuery notification system (simplified)
$(document).ready(function() {
  var unreadCount = 0;
  var notifications = [];
  
  function fetchNotifications() {
    $.ajax({
      url: '/api/notifications',
      success: function(data) {
        notifications = data;
        unreadCount = data.filter(function(n) { return !n.read; }).length;
        
        // Update badge in header
        if (unreadCount > 0) {
          $('#notification-badge').text(unreadCount).show();
        } else {
          $('#notification-badge').hide();
        }
        
        // Update notification dropdown list
        $('#notification-list').empty();
        data.forEach(function(notification) {
          var $item = $('<li>')
            .addClass(notification.read ? 'read' : 'unread')
            .text(notification.message)
            .click(function() {
              markAsRead(notification.id);
              $(this).removeClass('unread').addClass('read');
              unreadCount--;
              
              // MUST update badge here too — easy to forget!
              if (unreadCount > 0) {
                $('#notification-badge').text(unreadCount);
              } else {
                $('#notification-badge').hide();
              }
              
              // MUST update page title too — very easy to forget!
              document.title = unreadCount > 0 
                ? '(' + unreadCount + ') MyApp' 
                : 'MyApp';
            });
          $('#notification-list').append($item);
        });
        
        // Update page title
        document.title = unreadCount > 0 
          ? '(' + unreadCount + ') MyApp' 
          : 'MyApp';
          
        // Update mobile nav badge (another place to forget!)
        if (unreadCount > 0) {
          $('#mobile-badge').text(unreadCount).show();
        } else {
          $('#mobile-badge').hide();
        }
      }
    });
  }
  
  function markAsRead(id) {
    $.post('/api/notifications/' + id + '/read');
  }
  
  setInterval(fetchNotifications, 30000);
  fetchNotifications();
});

Count the number of places where unreadCount is used to update the DOM:

  1. #notification-badge text
  2. #notification-badge show/hide
  3. Page title
  4. #mobile-badge text
  5. #mobile-badge show/hide

Each click handler must update ALL of these. Forget one, and you have a bug. Add a new UI element that shows the count (e.g., a desktop notification) and you have to find every place that modifies unreadCount and add the new update.

What Developers Needed

INSTEAD OF:
  1. Something happened (user clicked, data arrived)
  2. Figure out what DOM nodes need to change
  3. Manually update each one
  4. Hope you didn't forget any
  5. Hope the order was right
  6. Hope no other code conflicts

DEVELOPERS WANTED:
  1. Something happened
  2. Update the DATA
  3. UI automatically reflects the new data
  4. Done — no manual DOM touching

4. Facebook's Specific Pain Point

The Notification Bug

In 2011-2012, Facebook had a persistent bug that became legendary in frontend circles. Users would see a notification badge showing "1 unread message" in the header. They'd click it, read the message, but the badge wouldn't clear. Sometimes it would come back after navigating away and returning.

This wasn't a simple bug — it was a systemic problem. Multiple teams managed different parts of the Facebook UI:

  • One team managed the chat sidebar
  • Another managed the notification dropdown
  • Another managed the top bar badges
  • Another managed the message page
  • Another managed the mobile web version

All of these had to stay in sync about the same underlying data: "How many unread messages does this user have?"

┌────────────────────────────────────────────┐
│              Facebook UI (2012)              │
├────────────────────────────────────────────┤
│                                              │
│  ┌────────────────────────────────────┐      │
│  │ Header:  Messages (1)  ← Team A   │      │
│  └────────────────────────────────────┘      │
│                                              │
│  ┌──────────────┐  ┌──────────────────┐      │
│  │ Feed         │  │ Chat Sidebar     │      │
│  │  ← Team B   │  │  New msg! ← Team │      │
│  │              │  │            C     │      │
│  │              │  │                  │      │
│  └──────────────┘  └──────────────────┘      │
│                                              │
│  Each team updates UI independently.         │
│  They share DATA but not UPDATE LOGIC.       │
│  Result: inconsistent UI everywhere.         │
│                                              │
└────────────────────────────────────────────┘

What Made It So Hard to Fix

The problem wasn't that the developers were bad — it was that the architecture made consistency impossible:

  1. No single source of truth — Each component had its own copy of the notification count
  2. Multiple update paths — The count could change from API polling, WebSocket messages, user actions, or navigation
  3. No automatic propagation — When one component updated the count, other components didn't know
  4. Race conditions — Multiple async operations could conflict with each other

Jordan Walke's Insight

Jordan Walke, a Facebook engineer, realized the core problem: the UI was too far from the data.

In traditional approaches, the DOM was the source of truth. You'd read from the DOM, modify the DOM, and hope everything stayed consistent. Jordan asked: What if we flipped this?

What if:

  • Data (JavaScript objects) is the single source of truth
  • The UI is a pure function that transforms data into DOM elements
  • Whenever data changes, you just re-run the function
  • A smart system figures out what actually changed in the DOM
// The conceptual model Jordan envisioned:
function renderApp(data) {
  return {
    header: {
      badge: data.unreadCount > 0 ? data.unreadCount : null
    },
    chatSidebar: {
      messages: data.recentMessages
    },
    feed: {
      posts: data.feedPosts.map(post => ({
        ...post,
        likeCount: data.likes[post.id]
      }))
    }
  };
}

// When data changes — just call renderApp() again.
// The framework diff's the old result with the new one.
// Only the actual DOM changes get applied.

This was the seed of React. The entire UI is re-described from scratch on every change, and a reconciliation algorithm figures out the minimal set of DOM operations needed.

From FaxJS to React

Jordan Walke's first implementation was called FaxJS (2011). It was a proof of concept that demonstrated the declarative re-rendering approach. Key experiments:

  1. Could you describe UI declaratively and have it work efficiently?
  2. Could you diff two UI descriptions to find minimal changes?
  3. Could this approach scale to Facebook's complexity?

The answer to all three was yes. FaxJS evolved into React, was deployed on Facebook's News Feed in 2012, then on Instagram, and was open-sourced in May 2013 at JSConf US.


5. The Birth of React — 2013

Timeline

YearEvent
2011Jordan Walke creates FaxJS, an early prototype
2012FaxJS evolves into React, used internally at Facebook
2012React powers Facebook's News Feed and Instagram
2013React open-sourced at JSConf US (May 29, 2013)
2014React Developer Tools released
2015React Native announced (mobile apps with React)
2015Redux introduced (state management companion)
2016React 15 — major performance improvements
2017React 16 — Fiber architecture (complete internal rewrite)
2018React 16.6 — React.lazy, React.memo, Suspense
2019React 16.8 — Hooks introduced (paradigm shift)
2020React 17 — No new features, gradual upgrade support
2022React 18 — Concurrent features, automatic batching, Suspense for data
2024React 19 — Server Components stable, Actions, new hooks

Initial Reception

React was controversial when it launched. The JavaScript community had two major objections:

Objection 1: "HTML in JavaScript?!"

// This horrified developers in 2013
function Button() {
  return <button className="btn">Click me</button>;
}

Developers had spent years learning "separation of concerns" — HTML in .html files, CSS in .css files, JavaScript in .js files. JSX seemed to violate this principle.

React's counter-argument: The real separation of concerns isn't by technology (HTML/CSS/JS), but by component (Button, Header, Modal). A button's structure, style, and behavior are inherently coupled — separating them into three files doesn't reduce complexity, it distributes it.

Objection 2: "Re-render everything?!"

// Wasn't this wasteful?
// State changes → entire component re-renders → new JSX → React updates DOM
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Header />           {/* Re-renders even though nothing changed? */}
      <Counter count={count} />
      <Footer />           {/* Re-renders even though nothing changed? */}
    </div>
  );
}

The Virtual DOM diffing algorithm made this surprisingly efficient. React doesn't re-render the actual DOM — it re-renders a lightweight JavaScript representation, diffs it against the previous one, and applies only the changes.

Why React Won Despite the Controversy

React won because it solved real problems that developers faced daily. The initial resistance faded as teams saw:

  1. Fewer bugs — single source of truth eliminated state-UI desync
  2. Faster development — components are reusable across the app
  3. Easier debugging — one-way data flow makes bugs traceable
  4. Better onboarding — new team members can understand isolated components without understanding the whole app

6. What React Actually Is

React is a JavaScript library for building user interfaces. Let's break down what each word means:

"Library" — Not a Framework

This distinction matters. A framework controls the flow of your application — it calls your code. A library is called by your code — you control the flow.

┌──────────────────────────────────────────────┐
│        FRAMEWORK vs LIBRARY                   │
├──────────────────────────────────────────────┤
│                                                │
│  FRAMEWORK (Angular, Next.js):                 │
│  ┌──────────────────────────────┐              │
│  │ Routing ✓                    │              │
│  │ HTTP client ✓                │              │
│  │ State management ✓           │              │
│  │ Form handling ✓              │              │
│  │ Testing utilities ✓          │              │
│  │ Build system ✓               │              │
│  │ UI rendering ✓               │              │
│  │ Dependency injection ✓       │              │
│  └──────────────────────────────┘              │
│  "We decide how your app is structured."       │
│  "Your code plugs into our system."            │
│                                                │
│  LIBRARY (React):                              │
│  ┌──────────────────────────────┐              │
│  │ UI rendering ✓               │              │
│  │ Everything else: your choice │              │
│  └──────────────────────────────┘              │
│  "We handle rendering. You decide the rest."   │
│  "You call us when you need UI."               │
│                                                │
└──────────────────────────────────────────────┘

Practical implication: React doesn't have opinions about:

  • How you fetch data (fetch, axios, TanStack Query — your choice)
  • How you handle routing (React Router, TanStack Router — your choice)
  • How you manage global state (Context, Zustand, Redux — your choice)
  • How you style components (CSS, Tailwind, styled-components — your choice)
  • How you structure your folders (your choice entirely)

Pro: Freedom to choose the best tool for each job. Con: Decision fatigue — you have to choose everything yourself.

This is why Next.js exists (Topic 2.18) — it wraps React with opinionated defaults for routing, rendering, and more, turning the library into a full framework.

"User Interfaces" — Not the Whole App

React handles the view layer — what the user sees and interacts with. It doesn't handle:

  • Data layer — How data gets to the browser (API calls, databases)
  • Navigation — How URLs map to pages (routing)
  • Business logic — Application rules and workflows
  • Infrastructure — Build, deployment, hosting

React's Actual Public API

React's API is surprisingly small. The entire library comes down to a few core concepts:

// 1. CREATE ELEMENTS
//    Raw API (you rarely use this directly):
React.createElement('div', { className: 'card' }, 'Hello');
//    JSX (syntactic sugar — what you actually write):
<div className="card">Hello</div>

// 2. CREATE COMPONENTS (functions that return elements)
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
    </div>
  );
}

// 3. MANAGE STATE (data that changes over time)
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);

// 4. HANDLE SIDE EFFECTS (things outside React's control)
useEffect(() => {
  document.title = `Count: ${count}`;
  return () => { /* cleanup */ };
}, [count]);

// 5. RENDER TO THE DOM (entry point — called once)
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

That's essentially it. Everything in the React ecosystem is built on top of these five concepts.


7. React's Core Philosophy

React is built on a few fundamental ideas that guide every design decision:

Philosophy 1: UI as a Function of State

This is the most important concept in React. If you remember one thing from this entire episode:

UI = f(state)

Your user interface is a pure function of your application's state (data). Given the same state, the function always produces the same UI output.

// state:
const state = {
  todos: [
    { id: 1, text: 'Learn React', completed: true },
    { id: 2, text: 'Build a project', completed: false },
  ],
  filter: 'active'
};

// f (the component):
function TodoList({ todos, filter }) {
  const visible = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });
  
  return (
    <ul>
      {visible.map(todo => (
        <li key={todo.id} className={todo.completed ? 'done' : ''}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

// UI (the output):
// <ul>
//   <li>Build a project</li>
// </ul>

Why this matters:

With manual DOMWith React
UI is a side effect of imperative commandsUI is a return value of a function
Hard to predict what UI looks like for given dataEasy — just call the function with that data
Bugs from forgetting to update some DOM nodeCan't happen — entire UI re-derived from state
Testing requires DOM simulationTesting is just "does this function return the right JSX?"

Philosophy 2: Composition Over Inheritance

In object-oriented programming, you often reuse code through inheritance — creating subclasses that extend base classes. React rejects this approach entirely for UI building.

Instead, you build complex UIs by composing simple components together:

// Small, focused, single-purpose components
function Avatar({ src, alt, size = 40 }) {
  return (
    <img 
      className="avatar" 
      src={src} 
      alt={alt} 
      width={size} 
      height={size} 
    />
  );
}

function UserName({ name, isOnline }) {
  return (
    <span className="username">
      {name}
      {isOnline && <span className="online-dot" />}
    </span>
  );
}

function Timestamp({ date }) {
  const formatted = new Date(date).toLocaleDateString('en-US', {
    month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
  });
  return <time className="timestamp">{formatted}</time>;
}

// Composed into a medium component
function CommentHeader({ author, date }) {
  return (
    <div className="comment-header">
      <Avatar src={author.avatarUrl} alt={author.name} />
      <UserName name={author.name} isOnline={author.isOnline} />
      <Timestamp date={date} />
    </div>
  );
}

// Composed into a larger component
function Comment({ author, date, text, replies }) {
  return (
    <article className="comment">
      <CommentHeader author={author} date={date} />
      <p className="comment-body">{text}</p>
      {replies.length > 0 && (
        <div className="replies">
          {replies.map(reply => (
            <Comment key={reply.id} {...reply} />
          ))}
        </div>
      )}
    </article>
  );
}

This is like building with LEGO blocks — each piece is simple, but you can combine them into anything.

Philosophy 3: Unidirectional Data Flow

Data in React flows in one direction — from parent to child. Events (user actions) flow in the opposite direction — from child to parent.

┌──────────────────────────────────────────┐
│        UNIDIRECTIONAL DATA FLOW          │
├──────────────────────────────────────────┤
│                                            │
│         ┌──────────┐                       │
│         │   App    │  ← state lives here   │
│         └─────┬────┘                       │
│       data ↓     ↓ data                    │
│    ┌───────┐   ┌───────┐                   │
│    │Header │   │ Main  │                   │
│    └───────┘   └───┬───┘                   │
│           data ↓      ↓ data               │
│        ┌───────┐   ┌───────┐              │
│        │ List  │   │Detail │              │
│        └───────┘   └───────┘              │
│                                            │
│  DATA:   Parent ──▶ Child (props)          │
│  EVENTS: Child  ──▶ Parent (callbacks)     │
│                                            │
│  Child needs to change data?               │
│  1. Parent passes callback function        │
│  2. Child calls the callback               │
│  3. Parent updates its state               │
│  4. New data flows down to all children    │
│                                            │
└──────────────────────────────────────────┘

Why one-way flow? Because it's predictable. When something goes wrong, you can trace the data from its source (the state) through the component tree to where it's displayed. Two-way binding (Angular 1, Backbone) makes this much harder:

TWO-WAY BINDING (hard to debug):
  Model ←→ View ←→ Model ←→ View
  "Where did this data change originate?" 🤷

ONE-WAY FLOW (easy to trace):
  State → Component → User Action → State Update → Re-render
  "The state changed in the click handler at line 42."

Philosophy 4: Explicit Over Implicit

React prefers explicit code over "magic":

// Angular (implicit — @Input decorator, ngOnChanges lifecycle)
@Component({
  template: '<p>{{fullName}}</p>'
})
class UserComponent {
  @Input() firstName: string;
  @Input() lastName: string;
  fullName: string;
  
  ngOnChanges() {
    this.fullName = `${this.firstName} ${this.lastName}`;
  }
}

// React (explicit — props, direct calculation)
function User({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;
  return <p>{fullName}</p>;
}

In React, you can see exactly where data comes from, how it's transformed, and what gets rendered. There's no hidden lifecycle, no magic decorators, no implicit subscriptions.


8. How React Solves the DOM Problem

Let's go back to the notification system and see how React handles it:

The Vanilla JS Approach (What We Had)

// Store data in separate variables
let unreadCount = 0;

// Cache DOM references
const badge = document.getElementById('badge');
const mobileBadge = document.getElementById('mobile-badge');
const dropdown = document.getElementById('notification-list');

// Manually update EVERY UI element on each change
function onNotificationRead(id) {
  unreadCount--;
  
  // Update desktop badge
  badge.textContent = unreadCount;
  badge.style.display = unreadCount > 0 ? 'block' : 'none';
  
  // Update mobile badge (easy to forget!)
  mobileBadge.textContent = unreadCount;
  mobileBadge.style.display = unreadCount > 0 ? 'block' : 'none';
  
  // Update page title
  document.title = unreadCount > 0 ? `(${unreadCount}) MyApp` : 'MyApp';
  
  // Update the specific list item
  const item = dropdown.querySelector(`[data-id="${id}"]`);
  if (item) {
    item.classList.remove('unread');
    item.classList.add('read');
  }
}

The React Approach

function NotificationCenter() {
  const [notifications, setNotifications] = useState([]);
  
  // Derived value — automatically correct
  const unreadCount = notifications.filter(n => !n.read).length;
  
  // Side effect — document title synced to unreadCount
  useEffect(() => {
    document.title = unreadCount > 0 ? `(${unreadCount}) MyApp` : 'MyApp';
  }, [unreadCount]);
  
  // Single action — everything else updates automatically
  function markAsRead(id) {
    setNotifications(prev =>
      prev.map(n => n.id === id ? { ...n, read: true } : n)
    );
  }
  
  return (
    <>
      {/* Desktop badge — automatically shows/hides */}
      <DesktopBadge count={unreadCount} />
      
      {/* Mobile badge — automatically shows/hides */}
      <MobileBadge count={unreadCount} />
      
      {/* Notification list — automatically updates */}
      <ul className="notification-list">
        {notifications.map(n => (
          <li
            key={n.id}
            className={n.read ? 'read' : 'unread'}
            onClick={() => markAsRead(n.id)}
          >
            {n.message}
          </li>
        ))}
      </ul>
    </>
  );
}

function DesktopBadge({ count }) {
  if (count === 0) return null;
  return <span className="badge desktop">{count}</span>;
}

function MobileBadge({ count }) {
  if (count === 0) return null;
  return <span className="badge mobile">{count}</span>;
}

What Changed — The Key Differences

AspectVanilla JSReact
Data locationScattered variablesuseState — single source of truth
Derived valuesMust manually compute & updateAutomatic — const unread = notifications.filter(...)
DOM updatesManual, imperative, easy to forgetAutomatic — React handles it
Adding new UIFind every place count is used, add updateJust use unreadCount in new component
Bug riskO(n) where n = number of DOM update sitesO(1) — only the state update logic
TestingNeed DOM environmentPure function — call with props, check output

The fundamental shift: you never think about "what DOM nodes need to change." You think about "what should the UI look like for this data?" React handles the rest.


9. React vs Vanilla JavaScript — A Real Comparison

Let's build a complete feature — a searchable, filterable product list — in both approaches.

Vanilla JavaScript Version

<!DOCTYPE html>
<html>
<body>
  <h1>Product Catalog</h1>
  
  <input id="search" placeholder="Search products..." />
  
  <select id="category-filter">
    <option value="all">All Categories</option>
    <option value="electronics">Electronics</option>
    <option value="clothing">Clothing</option>
    <option value="books">Books</option>
  </select>
  
  <select id="sort">
    <option value="name">Sort by Name</option>
    <option value="price-asc">Price: Low to High</option>
    <option value="price-desc">Price: High to Low</option>
  </select>
  
  <p id="result-count"></p>
  <div id="product-grid"></div>

  <script>
    const products = [
      { id: 1, name: 'Laptop', category: 'electronics', price: 999 },
      { id: 2, name: 'T-Shirt', category: 'clothing', price: 29 },
      { id: 3, name: 'JavaScript Book', category: 'books', price: 49 },
      { id: 4, name: 'Headphones', category: 'electronics', price: 199 },
      { id: 5, name: 'Jeans', category: 'clothing', price: 79 },
      { id: 6, name: 'React Book', category: 'books', price: 39 },
    ];
    
    let searchTerm = '';
    let categoryFilter = 'all';
    let sortBy = 'name';
    
    const searchInput = document.getElementById('search');
    const categorySelect = document.getElementById('category-filter');
    const sortSelect = document.getElementById('sort');
    const resultCount = document.getElementById('result-count');
    const grid = document.getElementById('product-grid');
    
    function getFilteredProducts() {
      let filtered = products;
      
      if (searchTerm) {
        filtered = filtered.filter(p => 
          p.name.toLowerCase().includes(searchTerm.toLowerCase())
        );
      }
      
      if (categoryFilter !== 'all') {
        filtered = filtered.filter(p => p.category === categoryFilter);
      }
      
      filtered.sort((a, b) => {
        if (sortBy === 'name') return a.name.localeCompare(b.name);
        if (sortBy === 'price-asc') return a.price - b.price;
        if (sortBy === 'price-desc') return b.price - a.price;
        return 0;
      });
      
      return filtered;
    }
    
    function render() {
      const filtered = getFilteredProducts();
      
      resultCount.textContent = `${filtered.length} product${filtered.length !== 1 ? 's' : ''} found`;
      
      grid.innerHTML = '';
      filtered.forEach(product => {
        const card = document.createElement('div');
        card.className = 'product-card';
        card.innerHTML = `
          <h3>${product.name}</h3>
          <p class="category">${product.category}</p>
          <p class="price">$${product.price}</p>
          <button data-id="${product.id}">Add to Cart</button>
        `;
        card.querySelector('button').addEventListener('click', () => {
          alert(`Added ${product.name} to cart!`);
        });
        grid.appendChild(card);
      });
    }
    
    searchInput.addEventListener('input', (e) => {
      searchTerm = e.target.value;
      render();
    });
    
    categorySelect.addEventListener('change', (e) => {
      categoryFilter = e.target.value;
      render();
    });
    
    sortSelect.addEventListener('change', (e) => {
      sortBy = e.target.value;
      render();
    });
    
    render();
  </script>
</body>
</html>

React Version

import { useState, useMemo } from 'react';

const PRODUCTS = [
  { id: 1, name: 'Laptop', category: 'electronics', price: 999 },
  { id: 2, name: 'T-Shirt', category: 'clothing', price: 29 },
  { id: 3, name: 'JavaScript Book', category: 'books', price: 49 },
  { id: 4, name: 'Headphones', category: 'electronics', price: 199 },
  { id: 5, name: 'Jeans', category: 'clothing', price: 79 },
  { id: 6, name: 'React Book', category: 'books', price: 39 },
];

function ProductCatalog() {
  const [search, setSearch] = useState('');
  const [category, setCategory] = useState('all');
  const [sortBy, setSortBy] = useState('name');
  
  const filtered = useMemo(() => {
    let result = PRODUCTS;
    
    if (search) {
      result = result.filter(p =>
        p.name.toLowerCase().includes(search.toLowerCase())
      );
    }
    
    if (category !== 'all') {
      result = result.filter(p => p.category === category);
    }
    
    return [...result].sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      if (sortBy === 'price-asc') return a.price - b.price;
      return b.price - a.price;
    });
  }, [search, category, sortBy]);
  
  return (
    <div>
      <h1>Product Catalog</h1>
      
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search products..."
      />
      
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
        <option value="books">Books</option>
      </select>
      
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">Sort by Name</option>
        <option value="price-asc">Price: Low to High</option>
        <option value="price-desc">Price: High to Low</option>
      </select>
      
      <p>{filtered.length} product{filtered.length !== 1 ? 's' : ''} found</p>
      
      <div className="product-grid">
        {filtered.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}

function ProductCard({ product }) {
  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p className="category">{product.category}</p>
      <p className="price">${product.price}</p>
      <button onClick={() => alert(`Added ${product.name} to cart!`)}>
        Add to Cart
      </button>
    </div>
  );
}

Side-by-Side Analysis

MetricVanilla JSReact
Total lines~80~65
DOM queries5 (getElementById, etc.)0
Event listener setup4 manual addEventListenerInline, declarative
innerHTML usageYes (XSS risk)No — JSX escapes by default
Memory managementManual (listeners on card buttons)Automatic (React cleanup)
ReusabilityNone (everything is monolithic)ProductCard is reusable
Adding a new filterAdd DOM element + listener + modify render()Add state variable + JSX
Type safetyNoneFull TypeScript support possible

10. Why Not Just Use Templates?

Some frameworks use HTML templates with special syntax:

<!-- Angular template -->
<ul>
  <li *ngFor="let todo of todos" [class.done]="todo.completed">
    {{ todo.text }}
  </li>
</ul>

<!-- Vue template -->
<ul>
  <li v-for="todo in todos" :key="todo.id" :class="{ done: todo.completed }">
    {{ todo.text }}
  </li>
</ul>

React chose JSX instead:

<ul>
  {todos.map(todo => (
    <li key={todo.id} className={todo.completed ? 'done' : ''}>
      {todo.text}
    </li>
  ))}
</ul>

The Template vs JSX Trade-off

AspectTemplates (Angular/Vue)JSX (React)
Iteration*ngFor, v-for (custom syntax)Array.map() (standard JS)
Conditionals*ngIf, v-if (custom syntax)&&, ? :, if/else (standard JS)
Data binding{{ }}, [], (){ } (single syntax)
Learning curveMust learn template DSLJust know JavaScript
Type checkingLimited (Angular has good support)Full TypeScript integration
DebuggingTemplate-specific error messagesStandard JS stack traces
RefactoringTemplate strings are hard to refactorRegular functions, IDE support
PowerLimited to what directives provideFull JavaScript expressiveness

Example: Complex Conditional Rendering

// React — just JavaScript logic
function UserDashboard({ user, subscription, notifications }) {
  if (!user) return <LoginPrompt />;
  
  const isTrialExpiring = subscription.type === 'trial' 
    && subscription.daysRemaining < 7;
  
  const criticalNotifications = notifications.filter(
    n => n.priority === 'critical' && !n.dismissed
  );
  
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      
      {isTrialExpiring && (
        <TrialWarning daysLeft={subscription.daysRemaining} />
      )}
      
      {criticalNotifications.length > 0 && (
        <AlertBanner notifications={criticalNotifications} />
      )}
      
      {user.role === 'admin' ? (
        <AdminPanel />
      ) : (
        <UserPanel subscription={subscription} />
      )}
    </div>
  );
}

Try expressing this in a template language — it's possible, but the logic gets awkward because you're fighting the template's limitations.


11. React's Ecosystem and Popularity

By the Numbers (2026)

MetricValue
npm weekly downloads~25 million
GitHub stars~230,000+
Stack Overflow questions~450,000+
Companies using itMeta, Netflix, Airbnb, Uber, Twitter/X, Discord, Notion, Shopify
Job postings#1 frontend library globally

The React Ecosystem Map

┌───────────────────────────────────────────────────┐
│              THE REACT ECOSYSTEM                    │
├───────────────────────────────────────────────────┤
│                                                     │
│  CORE                                               │
│  ├── React           (UI rendering engine)          │
│  └── ReactDOM        (browser DOM binding)          │
│                                                     │
│  ROUTING                                            │
│  ├── React Router    (declarative client routing)   │
│  └── TanStack Router (type-safe, file-based)        │
│                                                     │
│  STATE MANAGEMENT                                   │
│  ├── useState/useReducer  (local component state)   │
│  ├── Context API     (simple cross-component)       │
│  ├── Zustand         (lightweight global store)     │
│  ├── Redux Toolkit   (enterprise-grade store)       │
│  ├── Jotai           (atomic state model)           │
│  └── Recoil          (Facebook's atomic approach)   │
│                                                     │
│  SERVER STATE / DATA FETCHING                       │
│  ├── TanStack Query  (caching, sync, mutations)     │
│  ├── SWR             (stale-while-revalidate)       │
│  └── Apollo Client   (GraphQL integration)          │
│                                                     │
│  FORMS                                              │
│  ├── React Hook Form (performant, minimal)          │
│  ├── Zod             (schema validation)            │
│  └── Formik          (legacy, still used)           │
│                                                     │
│  STYLING                                            │
│  ├── Tailwind CSS    (utility-first CSS)            │
│  ├── CSS Modules     (scoped CSS)                   │
│  ├── styled-components (CSS-in-JS)                  │
│  └── Panda CSS       (build-time CSS-in-JS)         │
│                                                     │
│  FULL-STACK FRAMEWORKS (built on React)             │
│  ├── Next.js         (SSR, SSG, API routes)         │
│  ├── Remix           (nested routing, loaders)      │
│  └── Gatsby          (static sites, GraphQL)        │
│                                                     │
│  UI COMPONENT LIBRARIES                             │
│  ├── shadcn/ui       (copy-paste components)        │
│  ├── Radix UI        (headless, accessible)         │
│  ├── MUI             (Material Design)              │
│  └── Ant Design      (enterprise components)        │
│                                                     │
│  TESTING                                            │
│  ├── React Testing Library  (component testing)     │
│  ├── Vitest / Jest   (unit testing)                 │
│  └── Playwright      (end-to-end testing)           │
│                                                     │
│  BUILD TOOLS                                        │
│  ├── Vite            (fast dev server + bundler)    │
│  └── Turbopack       (Next.js bundler, Rust)        │
│                                                     │
│  MOBILE                                             │
│  └── React Native    (iOS + Android native apps)    │
│                                                     │
└───────────────────────────────────────────────────┘

React vs Competitors (2026)

FeatureReactVue 3Angular 17+Svelte 5Solid
TypeLibraryFrameworkFrameworkCompilerLibrary
RenderingVirtual DOMVirtual DOMChange detection + SignalsNo VDOM (compiled)Fine-grained reactivity
Bundle size~40KB~33KB~130KB+~2KB (compiled)~7KB
Learning curveModerateLow-ModerateHighLowLow-Moderate
Job market#1 globallyStrong in Asia/EUEnterprise/govGrowing nicheSmall but growing
TypeScriptExcellent (JSX)Good (templates limited)Built-in (decorators)Good (Runes)Excellent (JSX)
MobileReact NativeNativeScriptIonicSvelteNativeLimited
Backed byMetaCommunity (Evan You)GoogleCommunity (Rich Harris)Community
Server renderingVia Next.js/RemixNuxtAngular UniversalSvelteKitSolidStart

12. When React Is NOT the Right Choice

React is powerful but not universal. Choose the right tool for the job:

Skip React When:

1. Simple static websites (marketing pages, blogs)

  • Use: Astro, Hugo, 11ty, plain HTML/CSS
  • Why: React adds ~40KB+ of JavaScript for zero benefit on content-only pages

2. Minimal interactivity ("sprinkle of JS")

  • Use: htmx, Alpine.js, vanilla JavaScript
  • Why: Don't need a full component model for a dropdown menu and form validation

3. Performance-critical micro-widgets

  • Use: Preact (3KB React-compatible), Solid, or vanilla JS
  • Why: Every kilobyte matters when you're embedding in a third-party page

4. Team doesn't know JavaScript well

  • Use: Angular (more guardrails), or invest in JS training first
  • Why: React is "just JavaScript" — you need solid JS fundamentals

5. Server-rendered, low-JS applications

  • Use: Ruby on Rails + Hotwire, Laravel + Livewire, Django + htmx
  • Why: Full-stack server frameworks handle UI updates without client-side JS

Use React When:

  • Building interactive web applications with complex state
  • Multiple developers working on the same UI (components help collaboration)
  • You need a large ecosystem with solutions for every problem
  • You want code reuse between web and mobile (React Native)
  • Building a long-lived product (React's stability and backward compatibility are excellent)
  • Team knows JavaScript well (or will invest in learning)
  • You're building a SPA (single-page application) or a complex dashboard

13. React's Evolution — From Classes to Hooks

React has gone through three major API eras. Understanding this helps you read legacy code and appreciate current best practices.

Era 1: createClass (2013–2016)

// The original React API — no ES6 classes
var Counter = React.createClass({
  getInitialState: function() {
    return { count: 0 };
  },
  
  handleClick: function() {
    this.setState({ count: this.state.count + 1 });
  },
  
  render: function() {
    return React.createElement('div', null,
      React.createElement('p', null, 'Count: ' + this.state.count),
      React.createElement('button', { onClick: this.handleClick }, '+1')
    );
  }
});

Era 2: Class Components with ES6 (2015–2019)

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this); // 😤 binding issues
  }
  
  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }
  
  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }
  
  componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      document.title = `Count: ${this.state.count}`;
    }
  }
  
  componentWillUnmount() {
    document.title = 'MyApp';
  }
  
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
      </div>
    );
  }
}

Problems:

  • this binding is confusing and error-prone
  • Related logic split across lifecycle methods (title update in componentDidMount AND componentDidUpdate)
  • Sharing stateful logic between components required awkward patterns (HOCs, render props)
  • Classes are hard for bundlers to optimize (tree-shaking, minification)

Era 3: Hooks — Functional Components (2019–Present)

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    document.title = `Count: ${count}`;
    return () => { document.title = 'MyApp'; }; // cleanup
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

What hooks fixed:

  • No this keyword at all
  • Related logic stays together (title logic in one useEffect, not split across lifecycle methods)
  • Logic sharing via custom hooks (extract useDocumentTitle and reuse everywhere)
  • Functions are simpler to understand than classes
  • Better minification and optimization

Era 4: Server Components (2024+)

// This component runs on the SERVER — no JavaScript sent to the browser
async function ProductPage({ params }) {
  const product = await db.products.findById(params.id);
  const reviews = await db.reviews.findByProductId(params.id);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
      
      <ReviewList reviews={reviews} />
      
      {/* Only this component is a Client Component — only this ships JS */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}

Server Components are covered in depth in Topics 2.18–2.21.


14. The Mental Model Shift

The biggest challenge in learning React isn't the API — it's rewiring how you think about UI development.

Imperative Thinking (Pre-React)

You give the computer step-by-step instructions:

"When the user types in the search box..."
  1. Get the search box element
  2. Read its current value
  3. Filter the product array
  4. Clear the product grid
  5. For each filtered product, create a card element
  6. Add event listeners to each card's button
  7. Append each card to the grid
  8. Update the result count text

Declarative Thinking (React)

You describe what the end result should look like:

"The product grid shows filtered products based on the search term."
"The result count shows how many products are visible."
function ProductGrid({ products, searchTerm }) {
  const filtered = products.filter(p => 
    p.name.toLowerCase().includes(searchTerm.toLowerCase())
  );
  
  return (
    <div>
      <p>{filtered.length} results</p>
      {filtered.map(p => <ProductCard key={p.id} product={p} />)}
    </div>
  );
}

Analogies That Help

The Restaurant Analogy:

  • Imperative: You go into the kitchen. Preheat oven. Chop onions. Saut in olive oil for 3 minutes...
  • Declarative: "I'll have the pasta carbonara, please." The kitchen figures out how to make it.
  • React is the kitchen. You describe what you want (JSX). React figures out the DOM operations.

The Spreadsheet Analogy:

Cell A1: 10
Cell A2: 20  
Cell A3: =A1 + A2  → shows 30

Change A1 to 15 → A3 automatically shows 35. You never manually update A3.

React state = cells. React components = formulas. Change a state value → all dependent UI automatically updates.

The GPS Analogy:

  • Imperative directions: "Turn left at the gas station. Go 2 miles. Turn right at the school..."
  • Declarative destination: "Navigate to 123 Main Street." The GPS figures out the route.
  • If you make a wrong turn with imperative directions, you're lost. If you make a wrong turn with GPS, it recalculates. React recalculates your UI whenever state changes — you can't get "lost."

15. Key Takeaways

  1. React was born from real pain — Facebook's notification bug proved that manual DOM synchronization doesn't scale. React's solution: UI as a function of state.

  2. The core problem: Keeping data (JavaScript) and UI (DOM) in sync manually is error-prone, hard to maintain, and impossible to scale.

  3. jQuery helped but didn't solve the problem — It made DOM manipulation easier to write but not easier to manage at scale.

  4. React is a library, not a framework — It handles UI rendering only. You choose everything else (routing, state management, styling).

  5. UI = f(state) — Same state always produces the same UI. This makes React predictable, testable, and debuggable.

  6. Composition over inheritance — Build complex UIs by combining simple components, like LEGO blocks.

  7. Unidirectional data flow — Data flows down (props), events flow up (callbacks). This makes data flow traceable.

  8. JSX is "just JavaScript" — No special template syntax. Use map(), &&, ternary operators — your JS skills transfer directly.

  9. React's ecosystem is massive — Whatever you need (routing, forms, state, styling, testing), there's a well-maintained library for it.

  10. The mental shift is the hardest part — Move from "tell the DOM what to change" (imperative) to "describe what the UI should look like" (declarative). Once this clicks, everything else follows.


Explain-It Challenge

  1. Explain to a non-programmer why building a complex website is harder than it looks. Use the counter example — start simple, then add requirements and watch the complexity explode. What does React do to tame that complexity?

  2. Your friend says: "React just adds unnecessary complexity. I can build anything with vanilla JavaScript." How would you respond? In what cases would you actually agree with them?

  3. Explain UI = f(state) using a vending machine analogy. The vending machine's display (UI) is determined by what items are in stock (state). When stock changes, the display updates. How does this compare to manually changing the display every time someone buys something?


Navigation: ← Overview · Next → Declarative vs Imperative UI