React: Conditional Rendering and List Rendering
Last updated: 2026-10-26
Conditional rendering is like traffic lights—it determines what to display based on the state; list rendering is like assembly-line production—a list of data that generates identical UI components in batches.
1. What You'll Learn
- Use the ternary operator
? :to control the display content - Use
&&and||to simplify conditional rendering - Render the list using
.map() - The Importance of the "key" Attribute and Rules for Its Use
- Handling empty and loading states
2. The Front-End Story of an E-Commerce Platform
(1) Pain Point: The 6 States of a Product List
Alice is working on a product list page for an e-commerce platform. This page has six display states, and each time the state changes, she has to manually manipulate the DOM:
function renderProductList(state) {
const container = document.getElementById('product-list')
container.innerHTML = ''
if (state.isLoading) {
container.innerHTML = '<div class="loading">Loading......</div>'
} else if (state.error) {
container.innerHTML = '<div class="error">Failed to load</div>'
} else if (!state.isLoggedIn) {
container.innerHTML = '<div>Please log in</div>'
} else if (state.products.length === 0) {
container.innerHTML = '<div>No products available</div>'
} else {
let html = ''
state.products.forEach(p => {
html += `<div class="card"><h3>${p.name}</h3><p>$${p.price}</p></div>`
})
container.innerHTML = html
}
}
Alice's question: Every time the state changes, I have to manually clear the container and construct the HTML. 30 lines of code do just one thing—display the current state.
(2) Solutions for Conditional Rendering in React
function ProductList({ user, products, isLoading, error }) {
// Each state is independent if Branch,No interference with one another
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorMessage message={error} />
if (!user) return <LoginPrompt />
if (products.length === 0) return <EmptyState />
return (
<div className="product-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
Benefits: Just 15 lines of code cover all states, and each branch is easy to understand at a glance.
3. Three Ways to Use Conditional Rendering
| Method | Syntax | Use Cases | Notes |
|---|---|---|---|
| Ternary operator | condition ? A : B |
Choose one of two (with an "else" branch) | Nested ternary operators are hard to read; avoid more than two levels |
Logic and && |
condition && A |
Display only if condition is met (no "else") | 0 && <Comp /> will render 0; use > 0 |
| Logical OR ` | ` | `value |
(1) Ternary Operator ? :
Scenarios suitable for "choosing one of two options":
function StatusBadge({ isActive }) {
return (
<span style={{ color: isActive ? '#52c41a' : '#ff4d4f' }}>
{isActive ? '🟢 Online' : '🔴 Offline'}
</span>
)
}
// isActive=true → <span>🟢 Online</span>
// isActive=false → <span>🔴 Offline</span>
(2) Logic and &&
Suitable for "Display only if conditions are met; otherwise, do not display":
function Notification({ count }) {
return (
<div className="notification-icon">
🔔
{/* count > 0 A red dot appears,Otherwise, nothing will be displayed. */}
{count > 0 && (
<span className="badge">{count > 99 ? '99+' : count}</span>
)}
</div>
)
}
// count=0 → Show only 🔔
// count=5 → Show 🔔 and "5"
{items.length && <List />}. When items.length is 0, the result of 0 && <List /> is 0, and React will render 0 on the page. The correct way to write this is {items.length > 0 && <List />}.
(3) Logical OR ||
Suitable for "providing a fallback value":
function UserProfile({ user }) {
return (
<div>
<h2>{user.nickname || 'Anonymous User'}</h2>
<p>{user.bio || 'This person is lazy.,Nothing was written...'}</p>
<p>Location:{user.location || 'Unknown'}</p>
</div>
)
}
Output:
Side-by-side comparison: HTML syntax vs JSX syntax showing equivalent markup. TypeScript-typed React component with interface props
▶ Example: Side-by-Side Comparison of Three Types of Conditional Rendering
// ============================================
// Example:Combined Use of Three Types of Conditional Rendering in the User Panel
// ============================================
function UserPanel({ user }) {
return (
<div className="panel">
{/* 1. Ternary:Choose one of the two */}
<h2>
{user.isVIP ? '⭐ Premium Member' : 'Regular User'}
</h2>
{/* 2. &&:Display only when met */}
{user.isVIP && (
<p>Membership Expiration:{user.vipExpireDate}</p>
)}
{/* 3. ||:Floor value */}
<p>Signature:{user.slogan || 'This person is really cool.,No signature'}</p>
{/* Combined Use:List of Tags */}
{user.tags && user.tags.length > 0 && (
<div>
{user.tags.map(tag => (
<span key={tag} style={{ background: '#f0f0f0', padding: '2px 8px', margin: '2px', borderRadius: '4px' }}>
{tag}
</span>
))}
</div>
)}
</div>
)
}
Output:
If VIP: "⭐ Premium Member" + membership expiry. If not: "Regular User". Signature: user text or fallback. Tags shown only if present.
4. List Rendering: .map()
(1) Basic Usage
Array.map() Iterate through an array and return a JSX for each element:
(2) Array of rendering objects
▶ Try it Yourself(3) 3 Data Transformation Techniques for List Rendering
const items = [
{ id: 1, name: 'React Basics', price: 0 },
{ id: 2, name: 'Vue Getting Started', price: 0 },
{ id: 3, name: 'Node.js Real-World Experience', price: 99 }
]
// 1. Direct Rendering
{items.map(item => <li key={item.id}>{item.name}</li>)}
// 2. Filter First, Then Render(Free Courses)
{items
.filter(item => item.price === 0)
.map(item => <li key={item.id}>{item.name}(Free)</li>)
}
// 3. Extract Rendering Functions
function renderItem(item) {
return (
<li key={item.id}>
{item.name} - ${item.price}
</li>
)
}
{items.map(renderItem)}
5. The Importance of the Key
React uses key to identify each element in a list. When the key remains constant, React reuses the DOM node; when the key changes, React destroys and recreates it.
graph LR
A[List Changes] --> B{key Changes?}
B -->|No, key The same| C[Reuse DOM Node<br/>Update only the changes]
B -->|Yes, key It's changed| D[Delete Old Nodes<br/>Create a New Node]
B -->|No, None key| E[Render All Over]
Output:
Subheading: " {user.isVIP ? '⭐ Premium Member' : 'Regular User'} ". Displays: "0 && ("
| Key Strategy | Performance | Recommendation |
|---|---|---|
| Database Unique ID | ✅ Optimal | ⭐⭐⭐ |
| Unique string (UUID) | ✅ Best | ⭐⭐⭐ |
| Array index | ⚠️ Risky (state may become inconsistent when the list changes) | ⭐ |
| Random number | ❌ Range (varies with each render) | ❌ |
| No key passed | ❌ Poor performance | ❌ |
▶ Example: With a key vs. Without a key
▶ Try it YourselfOutput:
Two lists with input fields. Click "Randomize": ✅ list (key=id) keeps input values aligned; ❌ list (key=index) input values misalign with text
6. Empty State and Loading State
| Status | Condition | UI Behavior | Handling |
|---|---|---|---|
| Loading | loading === true |
Spinner / Skeleton Screen | if (loading) return <Spinner /> |
| Loading Failed | error !== null |
Error Message + Retry Button | if (error) return <ErrorView /> |
| Empty Data | data.length === 0 |
Empty State Illustration + Copy | if (!data.length) return <Empty /> |
| Normal | None of the above | Render data list | Default: return to normal UI |
In real-world projects, there are three states that must be handled when loading data:
// ============================================
// Example:Complete List Component(3 Handling Different States)
// ============================================
function ArticleList() {
const [articles, setArticles] = React.useState([])
const [isLoading, setIsLoading] = React.useState(true)
const [error, setError] = React.useState(null)
// Simulated Loading(Actually, it is API Call)
React.useEffect(() => {
fetch('/api/articles')
.then(res => {
if (!res.ok) throw new Error('Network request failed')
return res.json()
})
.then(data => {
setArticles(data)
setIsLoading(false)
})
.catch(err => {
setError(err.message)
setIsLoading(false)
})
}, [])
// 1. Loading...
if (isLoading) {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
<div className="spinner" />
<p>Loading article...</p>
</div>
)
}
// 2. Failed to load
if (error) {
return (
<div style={{ textAlign: 'center', padding: '40px', color: '#ff4d4f' }}>
<p>❌ Failed to load:{error}</p>
<button onClick={() => window.location.reload()}>Reload</button>
</div>
)
}
// 3. Empty data
if (articles.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
<p>📭 No articles available</p>
<p>No one has posted an article yet.,Go ahead and write your first one!!</p>
</div>
)
}
// 4. Normal Rendering
return (
<div>
<h3>List of Articles(Total {articles.length} articles)</h3>
{articles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</div>
)
}
7. Complete Example: Product Categories and Search
▶ Expand runnable example (81 lines)
Output:
Displays: "Randomize the order". Button: Randomize the order. Input: Enter content, The content may be misaligned
Expected Output: A complete product list page that supports search filtering, category filtering, empty result notifications, and statistics.
▶ Example 5: Dynamic Form Field Rendering
function DynamicForm() {
const [formType, setFormType] = useState('personal')
const [formData, setFormData] = useState({
name: '',
email: '',
company: '',
taxId: '',
budget: '',
})
function handleChange(e) {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}
const personalFields = (
<>
<input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
<input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
</>
)
const businessFields = (
<>
<input name="company" value={formData.company} onChange={handleChange} placeholder="Company" />
<input name="taxId" value={formData.taxId} onChange={handleChange} placeholder="Tax ID" />
<input name="budget" value={formData.budget} onChange={handleChange} placeholder="Budget ($)" />
</>
)
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Registration</h3>
<div style={{ marginBottom: 12 }}>
{['personal', 'business'].map(type => (
<button
key={type}
onClick={() => setFormType(type)}
style={{
padding: '6px 16px', marginRight: 8, cursor: 'pointer',
backgroundColor: formType === type ? '#1890ff' : '#f0f0f0',
color: formType === type ? 'white' : '#333', border: 'none', borderRadius: 4,
}}
>
{type === 'personal' ? 'Personal' : 'Business'}
</button>
))}
</div>
{formType === 'personal' ? personalFields : businessFields}
<button style={{ marginTop: 12, padding: '8px 24px', cursor: 'pointer' }}>Submit</button>
</div>
)
}
Output:
Personal form: Name + Email fields. Business form: Company + Tax ID + Budget fields. Toggle via buttons. Submit at bottom.
Output:
Search input filters items. Sort buttons: A-Z (by name) / Stars (by rating). Active sort highlighted blue. Filtered + sorted list updates reactively
▶ Example 3: Searching, Filtering, and Sorting Lists
▶ Try it YourselfOutput:
Frameworks: React ⭐220k, Vue ⭐207k, Tailwind ⭐80k, Django ⭐78k, Express ⭐63k. Search filters; A-Z/Stars sort toggles.
Output:
Displays "{cat.name}"
▶ Example 4: Rendering Nested Lists—Displaying Categorized Products
▶ Try it YourselfOutput:
Electronics: Keyboard $79, Monitor $399
Furniture: Desk $249, Chair $189
Books: "No products yet" (italicized empty state)
❓ FAQ
.map() be nested? How are keys handled when using multiple .map() calls?.map() must have its own key. For example, rendering "Category → Product: cats.map(c => <div key={c.id}><h3>{c.name}</h3>{c.products.map(p => <span key={p.id}>{p.name}</span>)}</div>)". Keys at different levels can be the same (as long as they are unique within their respective arrays).&& is suitable for “display if the condition is met; render nothing if not”; ternary ? : is suitable for “display component X if condition A is met; display component Y if condition B is met.” If you want to “display nothing when the condition isn’t met” using the ternary operator, the syntax would be condition ? <Comp /> : null, but && is more concise in this case. However, be aware of the “0 rendering trap” with &&—0 && <Comp /> will render a 0.📖 Summary
- There are three types of conditional rendering:
? :(either/or),&&(display only), and||(fallback value) - Use
.map()for list rendering; each JSX element must be bound to a uniquekey - Real-world projects must handle three states: loading, loading failed, and no data
- Beware of
{items.length && <List />}'s 0 rendering trap - Use a unique, stable ID as the key; do not use an index
📝 Exercises
- Basic Problem (Difficulty ⭐): Create a
TemperatureDisplaycomponent that, based on thetempprops, displays 🔥 in red when the temperature is >35°C, ☀️ in yellow when it is 15–35°C, and ❄️ in blue when it is <15°C. - Advanced Problem (Difficulty ⭐⭐): Create a
TodoFiltercomponent that accepts two props—todos(an array of tasks) andfilter(all/completed/uncompleted)—and implement filtering based on state. - Challenge (Difficulty: ⭐⭐⭐): Create a
Paginationcomponent that acceptstotalPages,currentPage, andonPageChangeprops and renders page number buttons, supporting the first and last pages as well as an ellipsis.