Understanding React — Components, the Virtual DOM and State

React is the “R” in MERN and the technology responsible for everything the user sees and interacts with in the browser. Unlike Angular — a complete framework that ships with routing, forms, HTTP client, and dependency injection — React is a focused UI library. Its single responsibility is rendering the right UI for a given state, and doing so efficiently. In this lesson you will understand how React’s component model works, how the virtual DOM keeps updates fast, what props and state are, and how React fits into the MERN architecture as the layer that consumes your Express API.

React vs Other Frontend Approaches

Approach Example How UI Updates Key Characteristic
Traditional HTML + JS jQuery Manually manipulate the DOM Imperative — you describe every DOM change step by step
Server-rendered PHP, EJS Full page reload on every change HTML built on the server, browser receives complete pages
React (SPA) React 18+ Virtual DOM diffing — minimal real DOM updates Declarative — describe what the UI should look like, React handles updates
Angular (SPA) Angular 17+ Change detection with signals / zone.js Full framework — includes routing, HTTP, DI, forms out of the box
Note: React is declarative. Instead of writing “find the element with ID ‘title’ and change its text to X”, you write “when the title state is X, the UI should look like this.” React figures out the minimum set of DOM changes needed to make the UI match the new state. This makes complex UIs much easier to reason about and debug.
Tip: For new MERN projects, scaffold your React frontend with Vite rather than Create React App (CRA). Vite starts in under 1 second regardless of project size, supports hot module replacement out of the box, and is actively maintained. CRA is officially deprecated. Use: npm create vite@latest client -- --template react
Warning: React does not come with a built-in HTTP client, router, or state management library. You must add these separately. For a MERN project you will typically add: React Router (routing), Axios (HTTP requests to Express), and optionally React Context or Zustand (global state). Do not confuse React the library with “the React ecosystem.”

The Component Model

Every piece of a React UI is a component — a JavaScript function that returns JSX (HTML-like syntax). Components are composable: you build complex UIs by nesting simpler components.

// A simple functional component
function PostCard({ title, author, createdAt }) {
  return (
    <div className="post-card">
      <h2>{title}</h2>
      <p>By {author} · {new Date(createdAt).toLocaleDateString()}</p>
    </div>
  );
}

// Composing components
function PostList({ posts }) {
  return (
    <div className="post-list">
      {posts.map(post => (
        <PostCard
          key={post._id}
          title={post.title}
          author={post.author}
          createdAt={post.createdAt}
        />
      ))}
    </div>
  );
}

Props vs State

Props State
What it is Data passed into a component from its parent Data owned and managed by the component itself
Who controls it Parent component The component itself
Can component change it? No — props are read-only Yes — via the useState setter function
Triggers re-render? Yes — when parent re-renders with new props Yes — whenever the setter is called with a new value
Example <PostCard title="Hello" /> const [posts, setPosts] = useState([])

The Virtual DOM

How React Updates the UI Efficiently
══════════════════════════════════════

1. State changes (e.g. new post added to posts array)
         │
         ▼
2. React re-renders the component tree in memory
   (creates a new Virtual DOM — a lightweight JS object tree)
         │
         ▼
3. React diffs the new Virtual DOM against the previous one
   (finds the minimum set of changes needed)
         │
         ▼
4. React applies ONLY the changed nodes to the real DOM
   (e.g. appends one <li> rather than re-rendering the entire list)
         │
         ▼
5. Browser paints the updated DOM — user sees the change

Result: Fast, efficient updates even in large, complex UIs

JSX — JavaScript + HTML Syntax

JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. It is compiled to plain JavaScript function calls by Vite/Babel before running in the browser.

// JSX (what you write)
const element = <h1 className="title">Hello, {user.name}!</h1>;

// What it compiles to (what the browser runs)
const element = React.createElement('h1', { className: 'title' }, `Hello, ${user.name}!`);

React in the MERN Data Flow

// Typical React component that fetches data from Express and renders it
import { useState, useEffect } from 'react';
import axios from 'axios';

function BlogPage() {
  const [posts,   setPosts]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [error,   setError]   = useState(null);

  useEffect(() => {
    axios.get('http://localhost:5000/api/posts')
      .then(res  => { setPosts(res.data.data); setLoading(false); })
      .catch(err => { setError(err.message);   setLoading(false); });
  }, []); // empty array = run once on mount

  if (loading) return <p>Loading posts...</p>;
  if (error)   return <p>Error: {error}</p>;

  return (
    <main>
      <h1>Blog</h1>
      {posts.map(post => <PostCard key={post._id} {...post} />)}
    </main>
  );
}

Common Mistakes

Mistake 1 — Mutating state directly

❌ Wrong — pushing to an array in state directly does not trigger a re-render:

posts.push(newPost);       // ← mutates state directly — React does not see this change
setPosts(posts);           // ← React sees the same array reference — no re-render

✅ Correct — always create a new array or object when updating state:

setPosts(prev => [...prev, newPost]); // new array reference → React re-renders ✓

Mistake 2 — Missing the key prop in lists

❌ Wrong — rendering lists without a unique key prop:

{posts.map(post => <PostCard title={post.title} />)}
// Warning: Each child in a list should have a unique "key" prop

✅ Correct — always use a stable unique identifier from your data:

{posts.map(post => <PostCard key={post._id} title={post.title} />)}

Mistake 3 — Calling the useState setter inside the render function

❌ Wrong — calling setState at the top level of a component causes an infinite render loop:

function MyComponent() {
  const [count, setCount] = useState(0);
  setCount(count + 1); // ← called every render → triggers re-render → infinite loop!
}

✅ Correct — state updates should only happen inside event handlers, useEffect, or async functions — never during the render itself.

Quick Reference

Concept Code
Create Vite + React project npm create vite@latest client -- --template react
Functional component function MyComp() { return <div>...</div> }
Pass props <PostCard title="Hello" author="Jane" />
Read props function PostCard({ title, author }) { ... }
Local state const [value, setValue] = useState(initialValue)
Run on mount useEffect(() => { fetchData() }, [])
Render a list {items.map(i => <Item key={i._id} {...i} />)}
Conditional render {isLoading && <Spinner />}

🧠 Test Yourself

A React component displays a list of posts. After adding a new post by calling posts.push(newPost) followed by setPosts(posts), the UI does not update. Why?