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 |
npm create vite@latest client -- --template reactThe 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 />} |