Master dynamic UI rendering with conditional logic and efficiently display lists of data. Learn React patterns for showing/hiding content and rendering dynamic collections.
Dynamic user interfaces are what make modern web applications engaging and interactive. In React, conditional rendering allows you to show different content based on state, props, or any condition, while list rendering lets you display collections of data efficiently.
Think of conditional rendering like traffic lights that show different colors based on conditions, or list rendering like a restaurant menu that displays all available dishes. These patterns are fundamental to creating responsive, data-driven applications.
In this lesson, you'll master both conditional rendering and list rendering, learning the patterns that power everything from simple to-do apps to complex data dashboards.
Conditional rendering allows you to display different JSX based on certain conditions. React provides several patterns to accomplish this.
1. If/Else with Early Return:
1function UserProfile({ user, isLoggedIn }) {
2 if (!isLoggedIn) {
3 return <div>Please log in to view your profile.</div>;
4 }
5
6 if (!user) {
7 return <div>Loading user data...</div>;
8 }
9
10 return (
11 <div className="profile">
12 <h2>Welcome, {user.name}!</h2>
13 <p>Email: {user.email}</p>
14 </div>
15 );
16}2. Ternary Operator (? :):
1function StatusMessage({ isOnline }) {
2 return (
3 <div>
4 <h2>User Status</h2>
5 {isOnline ? (
6 <span className="online">🟢 Online</span>
7 ) : (
8 <span className="offline">🔴 Offline</span>
9 )}
10 </div>
11 );
12}3. Logical AND (&&) for Show/Hide:
1function NotificationBell({ notifications }) {
2 return (
3 <div className="nav-item">
4 🔔
5 {notifications.length > 0 && (
6 <span className="badge">{notifications.length}</span>
7 )}
8 </div>
9 );
10}4. Switch-like Pattern with Objects:
1function TaskStatus({ status }) {
2 const statusConfig = {
3 pending: { icon: '⏳', text: 'Pending', color: 'orange' },
4 completed: { icon: '✅', text: 'Completed', color: 'green' },
5 failed: { icon: '❌', text: 'Failed', color: 'red' }
6 };
7
8 const config = statusConfig[status] || statusConfig.pending;
9
10 return (
11 <span style={{ color: config.color }}>
12 {config.icon} {config.text}
13 </span>
14 );
15}Complex Conditional Rendering:
1function Dashboard({ user, isLoading, error, data }) {
2 // Multiple conditions
3 if (isLoading) return <LoadingSpinner />;
4 if (error) return <ErrorMessage error={error} />;
5 if (!user) return <LoginPrompt />;
6 if (!user.hasPermission) return <AccessDenied />;
7 if (!data || data.length === 0) return <EmptyState />;
8
9 return <DashboardContent data={data} user={user} />;
10}The map() method is the primary way to render lists in React. It transforms an array of data into an array of JSX elements.
Basic List Rendering:
1function ShoppingList({ items }) {
2 return (
3 <div className="shopping-list">
4 <h2>Shopping List</h2>
5 <ul>
6 {items.map((item, index) => (
7 <li key={index}>{item}</li>
8 ))}
9 </ul>
10 </div>
11 );
12}
13
14// Usage
15const groceries = ['Apples', 'Bread', 'Milk', 'Eggs'];
16<ShoppingList items={groceries} />Rendering Object Arrays:
1function UserList({ users }) {
2 return (
3 <div className="user-list">
4 {users.map(user => (
5 <div key={user.id} className="user-card">
6 <img src={user.avatar} alt={user.name} />
7 <h3>{user.name}</h3>
8 <p>{user.email}</p>
9 <span className={`status ${user.isActive ? 'active' : 'inactive'}`}>
10 {user.isActive ? 'Active' : 'Inactive'}
11 </span>
12 </div>
13 ))}
14 </div>
15 );
16}
17
18const users = [
19 { id: 1, name: 'Alice', email: '[email protected]', isActive: true, avatar: '/alice.jpg' },
20 { id: 2, name: 'Bob', email: '[email protected]', isActive: false, avatar: '/bob.jpg' },
21 { id: 3, name: 'Charlie', email: '[email protected]', isActive: true, avatar: '/charlie.jpg' }
22];Complex List Rendering with Components:
1function ProductCard({ product, onAddToCart, onToggleFavorite }) {
2 return (
3 <div className="product-card">
4 <img src={product.image} alt={product.name} />
5 <h3>{product.name}</h3>
6 <p className="price">${product.price}</p>
7 <p className="description">{product.description}</p>
8 <div className="actions">
9 <button onClick={() => onAddToCart(product.id)}>
10 Add to Cart
11 </button>
12 <button
13 onClick={() => onToggleFavorite(product.id)}
14 className={product.isFavorite ? 'favorited' : ''}
15 >
16 {product.isFavorite ? '❤️' : '🤍'}
17 </button>
18 </div>
19 </div>
20 );
21}
22
23function ProductCatalog({ products, onAddToCart, onToggleFavorite }) {
24 return (
25 <div className="product-catalog">
26 <h2>Our Products ({products.length})</h2>
27 <div className="products-grid">
28 {products.map(product => (
29 <ProductCard
30 key={product.id}
31 product={product}
32 onAddToCart={onAddToCart}
33 onToggleFavorite={onToggleFavorite}
34 />
35 ))}
36 </div>
37 </div>
38 );
39}Keys are crucial for React's reconciliation algorithm. They help React identify which items have changed, been added, or removed.
Why Keys Matter:
1// ❌ BAD: Using index as key (problematic for dynamic lists)
2function BadTodoList({ todos }) {
3 return (
4 <ul>
5 {todos.map((todo, index) => (
6 <li key={index}> {/* Problems when list changes */}
7 <input type="checkbox" checked={todo.completed} />
8 <span>{todo.text}</span>
9 </li>
10 ))}
11 </ul>
12 );
13}
14
15// ✅ GOOD: Using stable, unique identifiers
16function GoodTodoList({ todos }) {
17 return (
18 <ul>
19 {todos.map(todo => (
20 <li key={todo.id}> {/* Stable unique identifier */}
21 <input type="checkbox" checked={todo.completed} />
22 <span>{todo.text}</span>
23 </li>
24 ))}
25 </ul>
26 );
27}Key Requirements:
Good Key Strategies:
1// Database IDs (best)
2key={item.id}
3
4// Unique combinations
5key={`${item.category}-${item.id}`}
6
7// Generated stable IDs
8key={item.uuid || generateId(item)}
9
10// Content-based (if content is unique)
11key={item.email} // for user list where email is uniqueBad Key Strategies:
1// ❌ Array index (problematic for dynamic lists)
2key={index}
3
4// ❌ Random values (changes every render)
5key={Math.random()}
6
7// ❌ New object creation
8key={{itemId: item.id}} // Creates new object each renderCombine conditional rendering with list rendering for powerful UI patterns.
Filtering and Searching:
1function FilterableList({ items, searchTerm, filter }) {
2 const filteredItems = items.filter(item => {
3 const matchesSearch = item.name.toLowerCase().includes(searchTerm.toLowerCase());
4 const matchesFilter = filter === 'all' || item.category === filter;
5 return matchesSearch && matchesFilter;
6 });
7
8 return (
9 <div>
10 <div className="list-header">
11 <h2>Items ({filteredItems.length} of {items.length})</h2>
12 {filteredItems.length === 0 && searchTerm && (
13 <p>No items match "{searchTerm}"</p>
14 )}
15 </div>
16
17 <div className="items-list">
18 {filteredItems.length > 0 ? (
19 filteredItems.map(item => (
20 <ItemCard key={item.id} item={item} />
21 ))
22 ) : (
23 <EmptyState message="No items to display" />
24 )}
25 </div>
26 </div>
27 );
28}Grouped Rendering:
1function GroupedList({ items }) {
2 const groupedItems = items.reduce((groups, item) => {
3 const group = item.category;
4 groups[group] = groups[group] || [];
5 groups[group].push(item);
6 return groups;
7 }, {});
8
9 return (
10 <div className="grouped-list">
11 {Object.entries(groupedItems).map(([category, categoryItems]) => (
12 <div key={category} className="category-group">
13 <h3>{category} ({categoryItems.length})</h3>
14 <div className="category-items">
15 {categoryItems.map(item => (
16 <ItemCard key={item.id} item={item} />
17 ))}
18 </div>
19 </div>
20 ))}
21 </div>
22 );
23}Nested Lists:
1function NestedMenu({ menuItems }) {
2 return (
3 <ul className="menu">
4 {menuItems.map(item => (
5 <li key={item.id} className="menu-item">
6 <span>{item.title}</span>
7 {item.children && item.children.length > 0 && (
8 <NestedMenu menuItems={item.children} />
9 )}
10 </li>
11 ))}
12 </ul>
13 );
14}Conditional List Rendering with State:
1function TodoApp() {
2 const [todos, setTodos] = useState([]);
3 const [filter, setFilter] = useState('all'); // all, active, completed
4
5 const filteredTodos = todos.filter(todo => {
6 if (filter === 'active') return !todo.completed;
7 if (filter === 'completed') return todo.completed;
8 return true; // 'all'
9 });
10
11 return (
12 <div className="todo-app">
13 <div className="filters">
14 {['all', 'active', 'completed'].map(filterType => (
15 <button
16 key={filterType}
17 onClick={() => setFilter(filterType)}
18 className={filter === filterType ? 'active' : ''}
19 >
20 {filterType} ({todos.filter(t =>
21 filterType === 'all' ? true :
22 filterType === 'active' ? !t.completed :
23 t.completed
24 ).length})
25 </button>
26 ))}
27 </div>
28
29 <div className="todo-list">
30 {filteredTodos.length === 0 ? (
31 <div className="empty-state">
32 {filter === 'all' && "No todos yet. Add one above!"}
33 {filter === 'active' && "No active todos. Great job! 🎉"}
34 {filter === 'completed' && "No completed todos yet."}
35 </div>
36 ) : (
37 filteredTodos.map(todo => (
38 <TodoItem key={todo.id} todo={todo} />
39 ))
40 )}
41 </div>
42 </div>
43 );
44}Build a comprehensive task manager demonstrating conditional rendering, list rendering, filtering, and dynamic UI updates.
1function TaskManager() {
2 const [tasks, setTasks] = useState([
3 { id: 1, title: 'Learn React Basics', category: 'Education', priority: 'high', completed: true, dueDate: '2024-01-15' },
4 { id: 2, title: 'Build Todo App', category: 'Project', priority: 'medium', completed: false, dueDate: '2024-01-20' },
5 { id: 3, title: 'Study JavaScript', category: 'Education', priority: 'high', completed: false, dueDate: '2024-01-18' },
6 { id: 4, title: 'Exercise', category: 'Health', priority: 'low', completed: false, dueDate: '2024-01-16' }
7 ]);
8
9 const [filter, setFilter] = useState('all');
10 const [searchTerm, setSearchTerm] = useState('');
11 const [sortBy, setSortBy] = useState('dueDate');
12 const [newTask, setNewTask] = useState({
13 title: '',
14 category: 'General',
15 priority: 'medium'
16 });
17
18 // Filtering and sorting logic
19 const filteredTasks = tasks
20 .filter(task => {
21 const matchesSearch = task.title.toLowerCase().includes(searchTerm.toLowerCase());
22 const matchesFilter =
23 filter === 'all' ? true :
24 filter === 'completed' ? task.completed :
25 filter === 'pending' ? !task.completed :
26 filter === task.priority || filter === task.category;
27 return matchesSearch && matchesFilter;
28 })
29 .sort((a, b) => {
30 if (sortBy === 'priority') {
31 const priorityOrder = { high: 3, medium: 2, low: 1 };
32 return priorityOrder[b.priority] - priorityOrder[a.priority];
33 }
34 if (sortBy === 'dueDate') {
35 return new Date(a.dueDate) - new Date(b.dueDate);
36 }
37 return a.title.localeCompare(b.title);
38 });
39
40 // Task actions
41 const addTask = () => {
42 if (!newTask.title.trim()) return;
43
44 const task = {
45 id: Date.now(),
46 ...newTask,
47 completed: false,
48 dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] // 7 days from now
49 };
50
51 setTasks(prev => [...prev, task]);
52 setNewTask({ title: '', category: 'General', priority: 'medium' });
53 };
54
55 const toggleTask = (id) => {
56 setTasks(prev => prev.map(task =>
57 task.id === id ? { ...task, completed: !task.completed } : task
58 ));
59 };
60
61 const deleteTask = (id) => {
62 setTasks(prev => prev.filter(task => task.id !== id));
63 };
64
65 const updateTaskPriority = (id, priority) => {
66 setTasks(prev => prev.map(task =>
67 task.id === id ? { ...task, priority } : task
68 ));
69 };
70
71 // Statistics
72 const stats = {
73 total: tasks.length,
74 completed: tasks.filter(t => t.completed).length,
75 pending: tasks.filter(t => !t.completed).length,
76 high: tasks.filter(t => t.priority === 'high' && !t.completed).length,
77 overdue: tasks.filter(t => !t.completed && new Date(t.dueDate) < new Date()).length
78 };
79
80 const categories = [...new Set(tasks.map(t => t.category))];
81 const priorities = ['high', 'medium', 'low'];
82
83 return (
84 <div className="task-manager">
85 <div className="header">
86 <h1>📋 Dynamic Task Manager</h1>
87 <p>Demonstrating Conditional Rendering & Lists</p>
88 </div>
89
90 {/* Statistics Dashboard */}
91 <div className="stats-grid">
92 <div className="stat-card">
93 <h3>Total Tasks</h3>
94 <div className="stat-number">{stats.total}</div>
95 </div>
96 <div className="stat-card completed">
97 <h3>Completed</h3>
98 <div className="stat-number">{stats.completed}</div>
99 </div>
100 <div className="stat-card pending">
101 <h3>Pending</h3>
102 <div className="stat-number">{stats.pending}</div>
103 </div>
104 <div className="stat-card urgent">
105 <h3>High Priority</h3>
106 <div className="stat-number">{stats.high}</div>
107 </div>
108 {stats.overdue > 0 && (
109 <div className="stat-card overdue">
110 <h3>⚠️ Overdue</h3>
111 <div className="stat-number">{stats.overdue}</div>
112 </div>
113 )}
114 </div>
115
116 {/* Add New Task */}
117 <div className="add-task-section">
118 <h2>Add New Task</h2>
119 <div className="add-task-form">
120 <input
121 type="text"
122 value={newTask.title}
123 onChange={(e) => setNewTask(prev => ({...prev, title: e.target.value}))}
124 placeholder="Enter task title..."
125 className="task-input"
126 />
127 <select
128 value={newTask.category}
129 onChange={(e) => setNewTask(prev => ({...prev, category: e.target.value}))}
130 className="task-select"
131 >
132 <option value="General">General</option>
133 <option value="Education">Education</option>
134 <option value="Project">Project</option>
135 <option value="Health">Health</option>
136 <option value="Work">Work</option>
137 </select>
138 <select
139 value={newTask.priority}
140 onChange={(e) => setNewTask(prev => ({...prev, priority: e.target.value}))}
141 className="task-select"
142 >
143 <option value="low">Low Priority</option>
144 <option value="medium">Medium Priority</option>
145 <option value="high">High Priority</option>
146 </select>
147 <button onClick={addTask} className="add-button">
148 ➕ Add Task
149 </button>
150 </div>
151 </div>
152
153 {/* Filters and Search */}
154 <div className="controls-section">
155 <div className="search-box">
156 <input
157 type="text"
158 value={searchTerm}
159 onChange={(e) => setSearchTerm(e.target.value)}
160 placeholder="🔍 Search tasks..."
161 className="search-input"
162 />
163 </div>
164
165 <div className="filter-buttons">
166 <h3>Filter by Status:</h3>
167 {['all', 'pending', 'completed'].map(filterType => (
168 <button
169 key={filterType}
170 onClick={() => setFilter(filterType)}
171 className={`filter-btn ${filter === filterType ? 'active' : ''}`}
172 >
173 {filterType === 'all' && `All (${stats.total})`}
174 {filterType === 'pending' && `Pending (${stats.pending})`}
175 {filterType === 'completed' && `Completed (${stats.completed})`}
176 </button>
177 ))}
178 </div>
179
180 <div className="filter-buttons">
181 <h3>Filter by Priority:</h3>
182 {priorities.map(priority => (
183 <button
184 key={priority}
185 onClick={() => setFilter(priority)}
186 className={`filter-btn priority-${priority} ${filter === priority ? 'active' : ''}`}
187 >
188 {priority} ({tasks.filter(t => t.priority === priority).length})
189 </button>
190 ))}
191 </div>
192
193 <div className="sort-controls">
194 <h3>Sort by:</h3>
195 <select
196 value={sortBy}
197 onChange={(e) => setSortBy(e.target.value)}
198 className="sort-select"
199 >
200 <option value="dueDate">Due Date</option>
201 <option value="priority">Priority</option>
202 <option value="title">Title</option>
203 </select>
204 </div>
205 </div>
206
207 {/* Task List */}
208 <div className="task-list-section">
209 <div className="list-header">
210 <h2>
211 Tasks ({filteredTasks.length}
212 {filteredTasks.length !== tasks.length && ` of ${tasks.length}`})
213 </h2>
214 {searchTerm && (
215 <p className="search-info">
216 Searching for: "{searchTerm}"
217 <button onClick={() => setSearchTerm('')} className="clear-search">
218 ❌ Clear
219 </button>
220 </p>
221 )}
222 </div>
223
224 {/* Conditional rendering based on filtered results */}
225 {filteredTasks.length === 0 ? (
226 <div className="empty-state">
227 {searchTerm ? (
228 <div>
229 <h3>🔍 No tasks found</h3>
230 <p>No tasks match "{searchTerm}"</p>
231 <button onClick={() => setSearchTerm('')} className="clear-search">
232 Clear search
233 </button>
234 </div>
235 ) : filter !== 'all' ? (
236 <div>
237 <h3>📝 No {filter} tasks</h3>
238 <p>You don't have any {filter} tasks yet.</p>
239 <button onClick={() => setFilter('all')} className="clear-filter">
240 Show all tasks
241 </button>
242 </div>
243 ) : (
244 <div>
245 <h3>🎉 No tasks yet!</h3>
246 <p>Add your first task above to get started.</p>
247 </div>
248 )}
249 </div>
250 ) : (
251 <div className="task-list">
252 {filteredTasks.map(task => {
253 const isOverdue = !task.completed && new Date(task.dueDate) < new Date();
254
255 return (
256 <div
257 key={task.id}
258 className={`task-item ${task.completed ? 'completed' : ''} ${isOverdue ? 'overdue' : ''}`}
259 >
260 <div className="task-main">
261 <input
262 type="checkbox"
263 checked={task.completed}
264 onChange={() => toggleTask(task.id)}
265 className="task-checkbox"
266 />
267 <div className="task-content">
268 <h3 className="task-title">{task.title}</h3>
269 <div className="task-meta">
270 <span className="task-category">{task.category}</span>
271 <span className={`task-priority priority-${task.priority}`}>
272 {task.priority} priority
273 </span>
274 <span className={`task-due ${isOverdue ? 'overdue' : ''}`}>
275 Due: {new Date(task.dueDate).toLocaleDateString()}
276 {isOverdue && ' ⚠️'}
277 </span>
278 </div>
279 </div>
280 </div>
281 <div className="task-actions">
282 <select
283 value={task.priority}
284 onChange={(e) => updateTaskPriority(task.id, e.target.value)}
285 className="priority-select"
286 >
287 <option value="low">Low</option>
288 <option value="medium">Medium</option>
289 <option value="high">High</option>
290 </select>
291 <button
292 onClick={() => deleteTask(task.id)}
293 className="delete-button"
294 >
295 🗑️
296 </button>
297 </div>
298 </div>
299 );
300 })}
301 </div>
302 )}
303 </div>
304
305 {/* Summary Footer */}
306 {tasks.length > 0 && (
307 <div className="summary-footer">
308 <p>
309 📊 Summary: {stats.completed} completed, {stats.pending} pending
310 {stats.overdue > 0 && `, ${stats.overdue} overdue`}
311 </p>
312 {stats.pending > 0 && (
313 <p>💪 Keep going! You're making great progress!</p>
314 )}
315 {stats.completed === tasks.length && tasks.length > 0 && (
316 <p>🎉 Congratulations! All tasks completed!</p>
317 )}
318 </div>
319 )}
320 </div>
321 );
322}This task manager demonstrates advanced conditional rendering and list patterns:
Conditional Rendering Examples:
List Rendering Features:
Advanced Patterns:
Key Learning Points:
Great work mastering dynamic UI patterns! Next, you'll learn about forms and controlled components:
You'll build sophisticated forms that provide excellent user experiences with real-time feedback and validation.
Your progress is saved in this browser, on this device only.