Introduction to React
Build UIs from components. The library that changed how we think about frontends.
Why React
Declarative UI, component reuse, and a massive ecosystem.
// The problem React solves:
// Vanilla JS: you tell the DOM WHAT TO DO (imperative)
document.querySelector(".count").textContent = count;
document.querySelector(".btn").addEventListener("click", ...);
// You manage every DOM update manually. Complex UIs = spaghetti.
// React: you describe WHAT IT SHOULD LOOK LIKE (declarative)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
// React figures out what DOM changes are needed.
// React's mental model:
// UI = f(state)
// Your UI is a function of your data.
// When data changes → React re-renders → DOM updates.
// Why React dominates:
// - Component model (reusable UI pieces)
// - Declarative (describe what, not how)
// - Virtual DOM (efficient updates)
// - Massive ecosystem (Next.js, React Native, etc.)
// - Job market (most in-demand frontend skill)
// Setup:
// $ pnpm create vite my-app --template react-ts
// $ cd my-app && pnpm install && pnpm devComponents & JSX
Functions that return UI. JSX makes HTML-in-JS natural.
// A component is just a function that returns JSX:
function Greeting() {
return <h1>Hello, World!</h1>;
}
// JSX looks like HTML but it's JavaScript:
// <h1>Hello</h1> compiles to:
// React.createElement("h1", null, "Hello")
// JSX rules:
// 1. Return ONE root element (or use Fragment <>...</>)
function Card() {
return (
<> {/* Fragment — no extra DOM node */}
<h2>Title</h2>
<p>Content</p>
</>
);
}
// 2. Close all tags (even self-closing)
// <img src="..." /> ← not <img src="...">
// <br /> ← not <br>
// 3. className instead of class
// <div className="container"> ← not class="container"
// 4. JavaScript expressions in curly braces:
const name = "Alice";
const element = <h1>Hello, {name}!</h1>;
const math = <p>{2 + 2}</p>; // renders: 4Components are functions returning JSX. JSX is syntactic sugar over React.createElement — it's JavaScript, not HTML.
1 / 2
Props
Pass data from parent to child. One-way data flow.
// Props = arguments to your component function:
function Button({ label, onClick, variant = "primary" }) {
return (
<button className={variant} onClick={onClick}>
{label}
</button>
);
}
// Using it:
<Button label="Save" onClick={handleSave} variant="primary" />
<Button label="Cancel" onClick={handleCancel} variant="secondary" />
// Props are READ-ONLY — never modify them:
function Bad({ items }) {
items.push("new"); // ❌ NEVER mutate props!
}
// children prop — content between tags:
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Usage:
<Card title="User Profile">
<p>This is the card content.</p>
<button>Edit</button>
</Card>
// TypeScript props:
interface ButtonProps {
label: string;
onClick: () => void;
variant?: "primary" | "secondary";
disabled?: boolean;
}
function Button({ label, onClick, variant = "primary", disabled }: ButtonProps) {
return <button className={variant} onClick={onClick} disabled={disabled}>{label}</button>;
}
// Data flows DOWN (parent → child)
// Events flow UP (child → parent via callbacks)
// This is "one-way data flow" — predictable and debuggableState
Data that changes over time. When state updates, React re-renders.
// useState — the most fundamental hook:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
// ↑ value ↑ updater ↑ initial value
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// State rules:
// 1. Call hooks at the TOP of your component (not inside if/loops)
// 2. State updates trigger a re-render
// 3. State is preserved between renders
// 4. Each component instance has its own state
// Object state — always spread to create new reference:
const [user, setUser] = useState({ name: "", email: "" });
// ❌ BAD — mutating existing object:
user.name = "Alice";
setUser(user); // React won't re-render (same reference!)
// ✓ GOOD — new object:
setUser({ ...user, name: "Alice" });
// Array state:
const [items, setItems] = useState([]);
// Add:
setItems([...items, newItem]);
// Remove:
setItems(items.filter(i => i.id !== targetId));
// Update one:
setItems(items.map(i => i.id === targetId ? { ...i, done: true } : i));
// Functional updates (when new state depends on old):
setCount(prev => prev + 1); // ✓ always gets latest valueRendering
How React decides what to update in the DOM.
// React rendering cycle:
// 1. State changes (setState called)
// 2. React calls your component function again
// 3. Returns new JSX (virtual DOM)
// 4. React DIFFS old vs new virtual DOM
// 5. Only changed DOM nodes are updated (reconciliation)
// Example:
function App() {
const [name, setName] = useState("World");
console.log("App rendered!"); // logs on every state change
return (
<div>
<h1>Hello, {name}!</h1> {/* updates text node only */}
<input onChange={e => setName(e.target.value)} />
<ExpensiveList /> {/* re-renders too! */}
</div>
);
}
// Hooks overview (the essential ones):
// useState — state that triggers re-renders
// useEffect — side effects (fetch data, subscriptions)
// useRef — mutable value that doesn't trigger re-render
// useMemo — cache expensive computations
// useCallback — cache function references
// useEffect — run code after render:
import { useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Runs after component renders:
fetch("/api/users/" + userId)
.then(r => r.json())
.then(data => setUser(data));
// Cleanup (runs before next effect or unmount):
return () => { /* cancel subscription, etc. */ };
}, [userId]); // ← dependency array: re-run when userId changes
if (!user) return <p>Loading...</p>;
return <h1>{user.name}</h1>;
}
// Dependency array rules:
// [] — run once on mount only
// [a, b] — run when a or b changes
// (omitted) — run after every render (rarely want this)FAQ
Common questions about React.