22 minLesson 16 of 40
React Fundamentals
Props, State & Re-rendering
Props, State & Re-rendering
Props and state are the two ways components hold data. Understanding the difference — and when React re-renders — is the foundation of building correct React applications.
Props
Props are data passed from parent to child. They're read-only:
// Parent passes props
function App() {
return <UserCard name="Alice" role="admin" isOnline={true} />;
}
// Child receives props as an object
function UserCard({ name, role, isOnline }) {
return (
<div className={`card ${isOnline ? "online" : ""}`}>
<h3>{name}</h3>
<span className="role">{role}</span>
</div>
);
}
// Default prop values
function Button({ label, variant = "primary", size = "md", disabled = false }) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
>
{label}
</button>
);
}
children Prop
// Pass JSX content between tags
function Card({ title, children }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="User Profile">
<img src={avatarUrl} alt="avatar" />
<p>Member since 2024</p>
<Button label="Edit Profile" />
</Card>
State with useState
State is data that belongs to a component. When state changes, React re-renders the component:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // [value, setter]
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. NEVER mutate state directly
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // ❌ direct mutation — React won't re-render
setItems([...items, 4]); // ✅ new array
// 2. State updates are async (batched)
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1); // reads stale count if called multiple times
setCount(count + 1); // still uses old value!
}
// Use updater function for derived state
function handleClick() {
setCount(prev => prev + 1); // ✅ uses latest value
setCount(prev => prev + 1); // ✅ correctly increments twice
}
// 3. Object state — spread to preserve other fields
const [user, setUser] = useState({ name: "Alice", age: 30, role: "admin" });
setUser({ ...user, age: 31 }); // ✅ keep name and role
setUser(prev => ({ ...prev, age: 31 })); // ✅ same, safer
State for Forms
function LoginForm() {
const [form, setForm] = useState({ email: "", password: "" });
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
function handleChange(e) {
setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
setError("");
try {
await login(form.email, form.password);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
{error && <p className="error">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? "Signing in..." : "Sign In"}
</button>
</form>
);
}
Lifting State Up
When two components need the same state, move it to their common parent:
function App() {
const [selectedId, setSelectedId] = useState(null);
return (
<div className="layout">
<UserList
selectedId={selectedId}
onSelect={setSelectedId} // pass setter down
/>
<UserDetail
userId={selectedId} // share the same state
/>
</div>
);
}
Re-rendering
React re-renders a component when:
- Its state changes
- Its parent re-renders (and passes new props)
- Context it consumes changes
// Avoid unnecessary re-renders
// If a prop is the same value, the component still re-renders by default
// (React.memo solves this — covered later)
function ExpensiveChart({ data }) {
console.log("Chart rendered");
return <svg>{/* ... */}</svg>;
}
// Parent re-renders → Chart re-renders even if data didn't change
// Use React.memo to skip if props are equal
const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
return <svg>{/* ... */}</svg>;
});
Next lesson: Hooks — useState, useEffect, and the rules of hooks.
📱
Get Notes Free →Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises