React: Conditional Rendering and List Rendering
Last updated: 2026-08-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>
)
}
▶ Example: Side-by-Side Comparison of Three Types of Conditional Rendering
Output:
Side-by-side comparison: HTML syntax vs JSX syntax showing equivalent markup. TypeScript-typed React component with interface props
// ============================================
// 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:
const fruits = ['Apple', 'Banana', 'Orange', 'Grapes']
function FruitList() {
return (
<ul>
{fruits.map(fruit => (
<li>{fruit}</li>
))}
</ul>
)
}
// Output:
// <ul>
// <li>Apple</li>
// <li>Banana</li>
// <li>Orange</li>
// <li>Grapes</li>
// </ul>
(2) Array of rendering objects
function ScoreTable() {
const students = [
{ id: 1, name: 'Alice', score: 92 },
{ id: 2, name: 'Bob', score: 85 },
{ id: 3, name: 'Charlie', score: 78 }
]
return (
<table border="1" cellPadding="8">
<thead>
<tr><th>Name</th><th>Fractions</th><th>Level</th></tr>
</thead>
<tbody>
{students.map(s => (
<tr key={s.id}>
<td>{s.name}</td>
<td>{s.score}</td>
<td>{s.score >= 90 ? 'Excellent' : s.score >= 80 ? 'Good' : 'Passing Grade'}</td>
</tr>
))}
</tbody>
</table>
)
}
(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]
| 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
Output:
Subheading: " {user.isVIP ? '⭐ Premium Member' : 'Regular User'} ". Displays: "0 && ("
// ============================================
// Example:key Impact on the Input Field's State
// ============================================
function KeyDemo() {
const [items, setItems] = React.useState([
{ id: 'a', text: 'Task A' },
{ id: 'b', text: 'Task B' },
{ id: 'c', text: 'Task C' }
])
function shuffle() {
setItems([...items].sort(() => Math.random() - 0.5))
}
return (
<div>
<button onClick={shuffle}>Randomize the order</button>
<p>✅ has key(id Stable,The content in the input field remains intact):</p>
<ul>
{items.map(item => (
<li key={item.id}>
{item.text} <input placeholder="Enter content" />
</li>
))}
</ul>
<p>❌ Without key(or index As key,The text in the input field appears jumbled):</p>
<ul>
{items.map((item, index) => (
<li key={index}>
{item.text} <input placeholder="The content may be misaligned" />
</li>
))}
</ul>
</div>
)
}
// Click"Randomize the order"after ,has key The content in the list input field remains in order
// use index List of,The content in the input field and the text are misaligned
Output:
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
// ============================================
// Complete Example:Product List(Conditional Rendering + Comprehensive Guide to List Rendering)
// Features:Filter by Category、Search、Prompt for empty results
// ============================================
function ShopPage() {
const [products] = React.useState([
{ id: 1, name: 'Wireless Mouse', category: 'Electronics', price: 89, sales: 2000 },
{ id: 2, name: 'Mechanical Keyboard', category: 'Electronics', price: 299, sales: 1500 },
{ id: 3, name: 'Notebook', category: 'Office', price: 15, sales: 5000 },
{ id: 4, name: 'Office Chair', category: 'Furniture', price: 899, sales: 800 },
{ id: 5, name: 'Desk Lamp', category: 'Furniture', price: 129, sales: 3000 },
{ id: 6, name: 'Monitor', category: 'Electronics', price: 1599, sales: 600 }
])
const [search, setSearch] = React.useState('')
const [category, setCategory] = React.useState('All')
// Filtering Logic
const filtered = products.filter(p => {
const matchSearch = p.name.includes(search)
const matchCategory = category === 'All' || p.category === category
return matchSearch && matchCategory
})
// Get the list of categories(Remove duplicates)
const categories = ['All', ...new Set(products.map(p => p.category))]
return (
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<h2>🛒 Product List</h2>
{/* Search box */}
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search by Product Name..."
style={{ width: '100%', padding: '8px', marginBottom: '12px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
/>
{/* Category Button(List Rendering) */}
<div style={{ marginBottom: '12px' }}>
{categories.map(cat => (
<button
key={cat}
onClick={() => setCategory(cat)}
style={{
padding: '6px 16px', margin: '0 4px',
backgroundColor: category === cat ? '#1890ff' : '#f0f0f0',
color: category === cat ? 'white' : '#333',
border: 'none', borderRadius: '4px', cursor: 'pointer'
}}
>
{cat}
</button>
))}
</div>
{/* Results Summary */}
{search || category !== 'All' ? (
<p style={{ color: '#666' }}>
{search && `Search"${search}" `}
{category !== 'All' && `/${category} `}
Total {filtered.length} Items
</p>
) : null}
{/* Empty result */}
{filtered.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
<p>😕 No matching products were found.</p>
<button
onClick={() => { setSearch(''); setCategory('All') }}
style={{ padding: '8px 16px', marginTop: '8px', cursor: 'pointer' }}
>
Clear All Filters
</button>
</div>
) : (
/* Product Grid(List Rendering) */
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px' }}>
{filtered.map(product => (
<div key={product.id} style={{ border: '1px solid #eee', borderRadius: '8px', padding: '16px' }}>
<div style={{ height: '120px', backgroundColor: '#f5f5f5', borderRadius: '4px', marginBottom: '8px' }} />
<h4>{product.name}</h4>
<p style={{ color: '#ff4d4f', fontWeight: 'bold', fontSize: '18px' }}>${product.price}</p>
<p style={{ color: '#999', fontSize: '13px' }}>Sold {product.sales} items</p>
<span style={{ background: '#f0f5ff', color: '#1890ff', padding: '2px 8px', borderRadius: '4px', fontSize: '12px' }}>
{product.category}
</span>
</div>
))}
</div>
)}
</div>
)
}
Expected Output: A complete product list page that supports search filtering, category filtering, empty result notifications, and statistics.
▶ Example 5: Dynamic Form Field Rendering
Output:
Displays: "Randomize the order". Button: Randomize the order. Input: Enter content, The content may be misaligned
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.
▶ Example 3: Searching, Filtering, and Sorting Lists
Output:
Search input filters items. Sort buttons: A-Z (by name) / Stars (by rating). Active sort highlighted blue. Filtered + sorted list updates reactively
function FilterableList() {
const [items] = useState([
{ id: 1, name: 'React', category: 'Frontend', stars: 220 },
{ id: 2, name: 'Django', category: 'Backend', stars: 78 },
{ id: 3, name: 'Vue', category: 'Frontend', stars: 207 },
{ id: 4, name: 'Express', category: 'Backend', stars: 63 },
{ id: 5, name: 'Tailwind', category: 'CSS', stars: 80 },
])
const [search, setSearch] = useState('')
const [sortKey, setSortKey] = useState('name')
const filtered = items
.filter(i => i.name.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => sortKey === 'stars' ? b.stars - a.stars : a.name.localeCompare(b.name))
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={search} onChange={e => setSearch(e.target.value)}
placeholder="Search frameworks..." style={{ width: '100%', padding: 8, borderRadius: 4, marginBottom: 8 }} />
<div style={{ marginBottom: 8 }}>
<button onClick={() => setSortKey('name')} style={{ padding: '4px 12px', marginRight: 4, cursor: 'pointer', background: sortKey === 'name' ? '#1890ff' : '#f0f0f0', color: sortKey === 'name' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>A-Z</button>
<button onClick={() => setSortKey('stars')} style={{ padding: '4px 12px', cursor: 'pointer', background: sortKey === 'stars' ? '#1890ff' : '#f0f0f0', color: sortKey === 'stars' ? 'white' : '#333', border: 'none', borderRadius: 4 }}>Stars</button>
</div>
{filtered.length === 0 ? (
<p style={{ color: '#999', textAlign: 'center' }}>No results</p>
) : (
<ul style={{ listStyle: 'none', padding: 0 }}>
{filtered.map(i => <li key={i.id} style={{ padding: 8, borderBottom: '1px solid #f0f0f0', display: 'flex', justifyContent: 'space-between' }}><span>{i.name} <small style={{ color: '#999' }}>({i.category})</small></span><span>⭐ {i.stars}k</span></li>)}
</ul>
)}
</div>
)
}
Output:
Frameworks: React ⭐220k, Vue ⭐207k, Tailwind ⭐80k, Django ⭐78k, Express ⭐63k. Search filters; A-Z/Stars sort toggles.
▶ Example 4: Rendering Nested Lists—Displaying Categorized Products
Output:
Displays "{cat.name}"
function CategorizedProducts() {
const categories = [
{ id: 'electronics', name: 'Electronics', products: [
{ id: 1, name: 'Keyboard', price: 79 },
{ id: 2, name: 'Monitor', price: 399 },
]},
{ id: 'furniture', name: 'Furniture', products: [
{ id: 3, name: 'Desk', price: 249 },
{ id: 4, name: 'Chair', price: 189 },
]},
{ id: 'books', name: 'Books', products: [] },
]
return (
<div style={{ maxWidth: 500, margin: '0 auto' }}>
{categories.map(cat => (
<div key={cat.id} style={{ marginBottom: 16 }}>
<h3 style={{ borderBottom: '2px solid #1890ff', paddingBottom: 4 }}>{cat.name}</h3>
{cat.products.length === 0 ? (
<p style={{ color: '#999', fontStyle: 'italic', paddingLeft: 16 }}>No products yet</p>
) : (
<ul style={{ listStyle: 'none', padding: 0, paddingLeft: 16 }}>
{cat.products.map(p => (
<li key={p.id} style={{ padding: 4, display: 'flex', justifyContent: 'space-between' }}>
<span>{p.name}</span><span>${p.price}</span>
</li>
))}
</ul>
)}
</div>
))}
</div>
)
}
Output:
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.