๐ Conditional Rendering Patterns
Not all components should render the same way all the time. Sometimes you show a login form, sometimes a user profile. Sometimes you display data, sometimes a loading spinner. Sometimes you show everything, sometimes just an error message. Conditional rendering is how you make your UI adapt to different states, data, and user actions. React gives you multiple ways to conditionally render content, and choosing the right pattern makes your code cleaner and easier to understand. Let's master all the patterns! ๐
๐ฏ Learning Objectives
By the end of this lesson, you will be able to:
- Use if/else statements for conditional rendering
- Apply ternary operators in JSX
- Use logical && and || operators effectively
- Implement switch statements for multiple conditions
- Use early returns and guard clauses
- Choose the right pattern for each scenario
- Handle loading, error, and empty states
- Avoid common conditional rendering pitfalls
- Write clean, readable conditional JSX
Estimated Time: 70-85 minutes
Project: Build a dashboard with multiple conditional sections
๐ In This Lesson
๐ฏ Conditional Rendering Basics
In React, conditional rendering works just like conditions in JavaScript - you decide what to render based on state, props, or other data.
๐ Definition
Conditional Rendering: Rendering different UI elements or components based on certain conditions, state, or props.
Why Conditional Rendering?
Real-World Examples
| Scenario | Condition | What to Render |
|---|---|---|
| User Authentication | Is user logged in? | Dashboard vs Login Form |
| Data Loading | Is data loaded? | Content vs Spinner |
| Permissions | Is user admin? | Admin Panel vs Normal View |
| Form Validation | Is form valid? | Submit Button vs Error Message |
| Feature Toggle | Is feature enabled? | New Feature vs Old Feature |
| Empty State | Is list empty? | Empty Message vs List Items |
The Basic Pattern
Simple Example
const Greeting: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
// Condition determines what renders
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please sign in</h1>;
}
};
Multiple Ways to Render Conditionally
๐ฏ Interactive: Which Pattern Should You Use?
Click through this decision tree to find the best conditional rendering pattern for your use case:
Each pattern has its own use case. Let's explore them all!
๐ If/Else Statements
The most straightforward way to conditionally render is using if/else statements.
Basic If/Else
Component-Level Conditionals
interface GreetingProps {
isLoggedIn: boolean;
}
const Greeting: React.FC<GreetingProps> = ({ isLoggedIn }) => {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please log in</h1>;
};
// Or with explicit else:
const Greeting: React.FC<GreetingProps> = ({ isLoggedIn }) => {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please log in</h1>;
}
};
Element Variables
Store JSX in Variables
const LoginControl: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
let button;
if (isLoggedIn) {
button = <button onClick={() => setIsLoggedIn(false)}>Logout</button>;
} else {
button = <button onClick={() => setIsLoggedIn(true)}>Login</button>;
}
return (
<div>
<h1>{isLoggedIn ? 'Welcome!' : 'Please sign in'}</h1>
{button}
</div>
);
};
If/Else If/Else Chain
Multiple Conditions
type Status = 'idle' | 'loading' | 'success' | 'error';
interface DataDisplayProps {
status: Status;
data?: any;
error?: string;
}
const DataDisplay: React.FC<DataDisplayProps> = ({ status, data, error }) => {
if (status === 'loading') {
return <div>Loading...</div>;
} else if (status === 'error') {
return <div>Error: {error}</div>;
} else if (status === 'success') {
return <div>Data: {JSON.stringify(data)}</div>;
} else {
return <div>No data yet</div>;
}
};
When to Use If/Else
| Use Case | Example |
|---|---|
| Complex conditions | Multiple checks needed |
| Early returns | Return different components entirely |
| Element variables | Calculate JSX before return |
| Readable logic | When inline operators would be messy |
โ Pro Tip
If/else is great when:
- You're returning completely different components
- The condition logic is complex
- You need to do calculations before rendering
โ Ternary Operator
The ternary operator (condition ? true : false) is perfect for inline conditional rendering in JSX.
Basic Ternary
Inline Conditionals
const Message: React.FC<{ isOnline: boolean }> = ({ isOnline }) => {
return (
<div>
{isOnline ? (
<span style={{ color: 'green' }}>โ Online</span>
) : (
<span style={{ color: 'gray' }}>โ Offline</span>
)}
</div>
);
};
// Or simpler:
const Status: React.FC<{ isActive: boolean }> = ({ isActive }) => (
<span>{isActive ? 'Active' : 'Inactive'}</span>
);
Ternary with Components
Rendering Different Components
const Dashboard: React.FC = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
{isLoggedIn ? (
<UserDashboard />
) : (
<LoginForm />
)}
</div>
);
};
// With props:
const Greeting: React.FC<{ user: User | null }> = ({ user }) => (
<div>
{user ? (
<h1>Hello, {user.name}!</h1>
) : (
<h1>Hello, Guest!</h1>
)}
</div>
);
Nested Ternaries
โ ๏ธ Be Careful with Nested Ternaries
// โ Hard to read
const Status = ({ status }) => (
<div>
{status === 'loading' ? (
<Spinner />
) : status === 'error' ? (
<Error />
) : status === 'empty' ? (
<Empty />
) : (
<Content />
)}
</div>
);
// โ
Better: Use if/else or switch
const Status = ({ status }) => {
if (status === 'loading') return <Spinner />;
if (status === 'error') return <Error />;
if (status === 'empty') return <Empty />;
return <Content />;
};
Ternary for Class Names and Styles
Conditional Styling
const Button: React.FC<{ isPrimary: boolean }> = ({ isPrimary }) => (
<button
className={isPrimary ? 'btn-primary' : 'btn-secondary'}
style={{
backgroundColor: isPrimary ? '#667eea' : '#f5f5f5',
color: isPrimary ? 'white' : '#333'
}}
>
Click me
</button>
);
// Multiple conditional classes
const Card: React.FC<{ isActive: boolean; isHovered: boolean }> = ({
isActive,
isHovered
}) => (
<div
className={`card ${isActive ? 'active' : ''} ${isHovered ? 'hovered' : ''}`}
>
Content
</div>
);
When to Use Ternary
| Use When | Example |
|---|---|
| Inline in JSX | Choosing between two components |
| Simple conditions | Boolean check with two outcomes |
| Conditional props | Different values based on condition |
| Short expressions | Both outcomes are brief |
๐ก Ternary Best Practices
- โ Keep it simple - one level deep
- โ Use parentheses for clarity
- โ Format for readability
- โ Avoid nesting ternaries
- โ Don't use for complex logic
๐ฎ Interactive: Ternary Operator Visualizer
Toggle the condition to see how the ternary operator evaluates:
โ Logical && Operator
Use && when you want to render something only if a condition is true (no else case needed).
Basic && Usage
Render or Nothing
const Notification: React.FC = () => {
const [hasNewMessages, setHasNewMessages] = useState(true);
const [messageCount, setMessageCount] = useState(5);
return (
<div>
<h1>Inbox</h1>
{/* Only show if condition is true */}
{hasNewMessages && (
<div className="alert">
You have new messages!
</div>
)}
{/* Show message count if greater than 0 */}
{messageCount > 0 && (
<p>{messageCount} unread messages</p>
)}
</div>
);
};
How && Works in JSX
Understanding the Logic
// In JavaScript:
true && 'Hello' // Returns 'Hello'
false && 'Hello' // Returns false
// In JSX:
{true && <div>Hello</div>} // Renders <div>Hello</div>
{false && <div>Hello</div>} // Renders nothing
// React ignores false, null, undefined, and true
// So false && <div> renders nothing!
Common && Patterns
Practical Examples
const UserProfile: React.FC<{ user: User | null }> = ({ user }) => (
<div>
{/* Show only if user exists */}
{user && (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)}
{/* Show only if user is admin */}
{user && user.role === 'admin' && (
<button>Admin Panel</button>
)}
{/* Show only if user has premium */}
{user?.isPremium && (
<div className="premium-badge">โญ Premium</div>
)}
</div>
);
const TodoList: React.FC<{ todos: Todo[] }> = ({ todos }) => (
<div>
{/* Show only if there are todos */}
{todos.length > 0 && (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)}
{/* Show only if list is empty */}
{todos.length === 0 && (
<p>No todos yet!</p>
)}
</div>
);
โ ๏ธ Common Pitfall: Falsy Values
Watch Out for 0 and ''
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
// โ Problem: When count is 0, it renders "0" on screen!
return (
<div>
{count && <p>Count: {count}</p>}
</div>
);
// When count = 0, React renders the 0!
// โ
Solution: Explicit comparison
return (
<div>
{count > 0 && <p>Count: {count}</p>}
</div>
);
};
// Another example:
const Items: React.FC<{ items: string[] }> = ({ items }) => (
<div>
{/* โ Shows "0" when empty */}
{items.length && <p>{items.length} items</p>}
{/* โ
Correct */}
{items.length > 0 && <p>{items.length} items</p>}
</div>
);
๐ฎ Interactive Demo: The && Operator Gotcha
Click the buttons below to see how the && operator behaves with different values. Notice what happens when the count reaches 0!
โ Buggy: count && ...
โ
Fixed: count > 0 && ...
๐ Logical || Operator (OR)
The logical OR operator (||) is useful for providing fallback values or default content when something is null, undefined, or falsy.
Basic Pattern
Providing Fallbacks
interface UserProps {
user?: {
name?: string;
avatar?: string;
};
}
const UserDisplay: React.FC<UserProps> = ({ user }) => (
<div className="user-card">
{/* Show user name or fallback */}
<h2>{user?.name || 'Anonymous User'}</h2>
{/* Show avatar or default */}
<img
src={user?.avatar || '/default-avatar.png'}
alt="User avatar"
/>
</div>
);
// Real-world example: Settings with defaults
interface SettingsProps {
theme?: 'light' | 'dark';
language?: string;
fontSize?: number;
}
const Settings: React.FC<SettingsProps> = ({
theme,
language,
fontSize
}) => (
<div className={`theme-${theme || 'light'}`}>
<p>Language: {language || 'English'}</p>
<p>Font Size: {fontSize || 16}px</p>
</div>
);
Nullish Coalescing (??)
๐ก Modern Alternative
The nullish coalescing operator (??) is better than || when you want to treat 0, false, and empty strings as valid values:
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
// โ Problem with ||: 0 is considered falsy
return <p>Count: {count || 'No count'}</p>;
// Renders: "Count: No count" when count is 0!
// โ
Solution with ??: only null/undefined are replaced
return <p>Count: {count ?? 'No count'}</p>;
// Renders: "Count: 0" correctly!
};
// More examples:
const Examples: React.FC = () => {
const emptyString = '';
const zero = 0;
const falseValue = false;
return (
<div>
{/* With || */}
<p>{emptyString || 'default'}</p> {/* Shows: default */}
<p>{zero || 'default'}</p> {/* Shows: default */}
<p>{falseValue || 'default'}</p> {/* Shows: default */}
{/* With ?? */}
<p>{emptyString ?? 'default'}</p> {/* Shows: (empty) */}
<p>{zero ?? 'default'}</p> {/* Shows: 0 */}
<p>{falseValue ?? 'default'}</p> {/* Shows: false */}
</div>
);
};
Chaining || for Multiple Fallbacks
Multiple Default Options
interface DataDisplayProps {
primaryData?: string;
secondaryData?: string;
tertiaryData?: string;
}
const DataDisplay: React.FC<DataDisplayProps> = ({
primaryData,
secondaryData,
tertiaryData
}) => (
<div>
{/* Try multiple sources in order */}
<p>{primaryData || secondaryData || tertiaryData || 'No data available'}</p>
</div>
);
// Example with user profiles
interface ProfileProps {
displayName?: string;
username?: string;
email?: string;
}
const ProfileHeader: React.FC<ProfileProps> = ({
displayName,
username,
email
}) => (
<div className="profile-header">
<h1>{displayName || username || email?.split('@')[0] || 'User'}</h1>
</div>
);
โ Best Practice
Use || for default strings/objects, but use ?? when 0, false, or empty string are valid values you want to display.
๐ฎ Interactive: || vs ?? Comparison
Click different values to see how || and ?? behave differently:
value || 'default'
value ?? 'default'
๐ Switch Statements
When you have multiple conditions based on a single value (like status, role, or type), switch statements can be cleaner than multiple if/else statements.
Basic Switch Pattern
Status-Based Rendering
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
interface OrderProps {
status: OrderStatus;
orderId: string;
}
const OrderStatusDisplay: React.FC<OrderProps> = ({ status, orderId }) => {
// Helper function using switch
const getStatusDisplay = () => {
switch (status) {
case 'pending':
return (
<div className="status status-pending">
<h3>โณ Order Pending</h3>
<p>Waiting for confirmation...</p>
</div>
);
case 'processing':
return (
<div className="status status-processing">
<h3>๐ฆ Processing Order</h3>
<p>Your order is being prepared.</p>
</div>
);
case 'shipped':
return (
<div className="status status-shipped">
<h3>๐ Order Shipped</h3>
<p>Your package is on its way!</p>
</div>
);
case 'delivered':
return (
<div className="status status-delivered">
<h3>โ
Delivered</h3>
<p>Order #{orderId} has been delivered.</p>
</div>
);
case 'cancelled':
return (
<div className="status status-cancelled">
<h3>โ Order Cancelled</h3>
<p>This order has been cancelled.</p>
</div>
);
default:
return <p>Unknown status</p>;
}
};
return (
<div className="order-status">
{getStatusDisplay()}
</div>
);
};
Object Mapping Pattern (Better!)
๐ก More Elegant Approach
Instead of switch statements, you can use object mapping for cleaner, more maintainable code:
type AlertType = 'info' | 'success' | 'warning' | 'error';
interface AlertProps {
type: AlertType;
message: string;
}
const Alert: React.FC<AlertProps> = ({ type, message }) => {
// Map types to configurations
const alertConfig = {
info: {
icon: 'โน๏ธ',
className: 'alert-info',
title: 'Information'
},
success: {
icon: 'โ
',
className: 'alert-success',
title: 'Success'
},
warning: {
icon: 'โ ๏ธ',
className: 'alert-warning',
title: 'Warning'
},
error: {
icon: 'โ',
className: 'alert-error',
title: 'Error'
}
};
const config = alertConfig[type];
return (
<div className={`alert ${config.className}`}>
<span className="alert-icon">{config.icon}</span>
<div className="alert-content">
<h4>{config.title}</h4>
<p>{message}</p>
</div>
</div>
);
};
Complex Switch with JSX
User Role-Based Dashboard
type UserRole = 'admin' | 'moderator' | 'user' | 'guest';
interface DashboardProps {
role: UserRole;
username: string;
}
const Dashboard: React.FC<DashboardProps> = ({ role, username }) => {
const renderDashboard = () => {
switch (role) {
case 'admin':
return (
<div className="dashboard admin-dashboard">
<h1>๐ Admin Dashboard</h1>
<p>Welcome, {username}!</p>
<ul>
<li>๐ฅ Manage Users</li>
<li>โ๏ธ System Settings</li>
<li>๐ Analytics</li>
<li>๐ Security</li>
</ul>
</div>
);
case 'moderator':
return (
<div className="dashboard moderator-dashboard">
<h1>๐ก๏ธ Moderator Dashboard</h1>
<p>Welcome, {username}!</p>
<ul>
<li>๐ Review Content</li>
<li>๐ซ Moderate Posts</li>
<li>๐ฌ User Reports</li>
</ul>
</div>
);
case 'user':
return (
<div className="dashboard user-dashboard">
<h1>๐ค User Dashboard</h1>
<p>Welcome back, {username}!</p>
<ul>
<li>๐ My Posts</li>
<li>โญ Favorites</li>
<li>๐ Notifications</li>
</ul>
</div>
);
case 'guest':
return (
<div className="dashboard guest-dashboard">
<h1>๐ Welcome, Guest!</h1>
<p>Please sign in to access more features.</p>
<button>Sign In</button>
</div>
);
default:
const _exhaustive: never = role;
return <div>Unknown role</div>;
}
};
return renderDashboard();
};
โ When to Use Switch vs Object Mapping
| Pattern | Best For | Example |
|---|---|---|
| Switch Statement | Complex rendering logic, different components per case | Rendering completely different layouts |
| Object Mapping | Same structure with different data/config | Alert types, status badges, icons |
โฉ๏ธ Early Returns
Early returns (also called guard clauses) are a powerful pattern where you return JSX early based on certain conditions, keeping the main component logic clean and readable.
Basic Early Return Pattern
Loading and Error States
interface DataDisplayProps {
data: string[] | null;
isLoading: boolean;
error: string | null;
}
const DataDisplay: React.FC<DataDisplayProps> = ({ data, isLoading, error }) => {
// Early return for loading
if (isLoading) {
return (
<div className="loading">
<p>โณ Loading data...</p>
</div>
);
}
// Early return for error
if (error) {
return (
<div className="error">
<h3>โ Error</h3>
<p>{error}</p>
</div>
);
}
// Early return for empty data
if (!data || data.length === 0) {
return (
<div className="empty">
<p>๐ญ No data available</p>
</div>
);
}
// Main render - we know we have valid data!
return (
<div className="data-display">
<h2>Data List</h2>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
Why Early Returns Are Better
๐ก Compare: Nested vs Early Returns
// โ Nested conditionals (hard to read)
const UserProfile: React.FC<{ user: User | null }> = ({ user }) => {
return (
<div>
{user ? (
user.isPremium ? (
user.hasCompletedProfile ? (
<div>
<h1>{user.name}</h1>
<PremiumBadge />
{/* Complex profile JSX */}
</div>
) : (
<CompleteProfilePrompt />
)
) : (
<UpgradeToPremium />
)
) : (
<SignInPrompt />
)}
</div>
);
};
// โ
Early returns (much cleaner!)
const UserProfile: React.FC<{ user: User | null }> = ({ user }) => {
// Handle each special case first
if (!user) {
return <SignInPrompt />;
}
if (!user.isPremium) {
return <UpgradeToPremium />;
}
if (!user.hasCompletedProfile) {
return <CompleteProfilePrompt />;
}
// Main happy path - no nesting!
return (
<div>
<h1>{user.name}</h1>
<PremiumBadge />
{/* Complex profile JSX */}
</div>
);
};
Real-World Example: Auth Flow
Protected Component
interface ProtectedPageProps {
isAuthenticated: boolean;
isLoading: boolean;
hasPermission: boolean;
user: User | null;
}
const ProtectedPage: React.FC<ProtectedPageProps> = ({
isAuthenticated,
isLoading,
hasPermission,
user
}) => {
// Check loading first
if (isLoading) {
return (
<div className="loading-screen">
<div className="spinner"></div>
<p>Checking credentials...</p>
</div>
);
}
// Check authentication
if (!isAuthenticated) {
return (
<div className="auth-required">
<h2>๐ Authentication Required</h2>
<p>Please log in to view this page.</p>
<button>Log In</button>
</div>
);
}
// Check permissions
if (!hasPermission) {
return (
<div className="no-permission">
<h2>๐ซ Access Denied</h2>
<p>You don't have permission to view this page.</p>
<button>Go Back</button>
</div>
);
}
// Null check (though TypeScript knows it's not null here)
if (!user) {
return <div>Error: User data missing</div>;
}
// Main protected content - we're good to go!
return (
<div className="protected-content">
<h1>Welcome, {user.name}! ๐</h1>
<p>This is premium protected content.</p>
{/* Protected features */}
</div>
);
};
โ Benefits of Early Returns
- Reduces nesting: No deeply nested ternaries or conditionals
- Improves readability: Each condition is clear and isolated
- Easier debugging: You can add console.logs or breakpoints easily
- Better error handling: Edge cases are handled first
- Clean happy path: Main logic isn't cluttered with conditionals
๐ Visual: Nested vs Early Returns Code Structure
Hover over each section to see how the code structure differs:
Early returns keep your main logic clean and visible, not buried under layers of conditions.
๐จ Common Rendering Patterns
Let's explore real-world patterns you'll use constantly in React development.
๐ฎ Interactive: State Machine Simulator
Click the buttons to simulate different states and see how your component should respond:
Pattern 1: Loading โ Error โ Empty โ Content
The Standard Data Flow
interface User {
id: string;
name: string;
email: string;
}
interface UserListProps {
users: User[];
isLoading: boolean;
error: string | null;
}
const UserList: React.FC<UserListProps> = ({ users, isLoading, error }) => {
// 1. Loading state
if (isLoading) {
return (
<div className="user-list-loading">
<div className="spinner"></div>
<p>Loading users...</p>
</div>
);
}
// 2. Error state
if (error) {
return (
<div className="user-list-error">
<h3>โ Error Loading Users</h3>
<p>{error}</p>
<button>Try Again</button>
</div>
);
}
// 3. Empty state
if (users.length === 0) {
return (
<div className="user-list-empty">
<h3>๐ญ No Users Found</h3>
<p>There are no users to display.</p>
</div>
);
}
// 4. Content (happy path)
return (
<div className="user-list">
<h2>Users ({users.length})</h2>
<ul>
{users.map(user => (
<li key={user.id}>
<strong>{user.name}</strong>
<br />
{user.email}
</li>
))}
</ul>
</div>
);
};
Pattern 2: Feature Flags
Toggle Features On/Off
interface FeatureFlags {
showNewDesign: boolean;
enableComments: boolean;
enableSharing: boolean;
}
interface ArticleProps {
title: string;
content: string;
features: FeatureFlags;
}
const Article: React.FC<ArticleProps> = ({ title, content, features }) => (
<article className={features.showNewDesign ? 'new-design' : 'old-design'}>
<h1>{title}</h1>
<div>{content}</div>
{/* Show comments section if enabled */}
{features.enableComments && (
<section className="comments">
<h3>๐ฌ Comments</h3>
<CommentList />
</section>
)}
{/* Show sharing buttons if enabled */}
{features.enableSharing && (
<div className="share-buttons">
<button>Share on Twitter</button>
<button>Share on Facebook</button>
</div>
)}
</article>
);
Pattern 3: Skeleton Screens
Better Than Spinners
interface ProfileCardProps {
user: User | null;
isLoading: boolean;
}
const ProfileCard: React.FC<ProfileCardProps> = ({ user, isLoading }) => {
// Show skeleton while loading
if (isLoading) {
return (
<div className="profile-card skeleton">
<div className="skeleton-avatar"></div>
<div className="skeleton-text skeleton-text-long"></div>
<div className="skeleton-text skeleton-text-short"></div>
</div>
);
}
if (!user) {
return <div>No user data</div>;
}
return (
<div className="profile-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.bio}</p>
</div>
);
};
Pattern 4: Optimistic UI
Show Changes Before Confirmation
const LikeButton: React.FC = () => {
const [isLiked, setIsLiked] = useState(false);
const [likeCount, setLikeCount] = useState(42);
const [isPending, setIsPending] = useState(false);
const handleLike = async () => {
// Optimistic update
const previousState = isLiked;
const previousCount = likeCount;
setIsLiked(!isLiked);
setLikeCount(isLiked ? likeCount - 1 : likeCount + 1);
setIsPending(true);
try {
await api.toggleLike();
} catch (error) {
// Revert on error
setIsLiked(previousState);
setLikeCount(previousCount);
} finally {
setIsPending(false);
}
};
return (
<button
onClick={handleLike}
className={isLiked ? 'liked' : 'not-liked'}
disabled={isPending}
>
{isLiked ? 'โค๏ธ' : '๐ค'} {likeCount}
</button>
);
};
Pattern 5: Permission-Based Rendering
Show UI Based on User Permissions
type Permission = 'read' | 'write' | 'delete' | 'admin';
interface DocumentProps {
document: Document;
userPermissions: Permission[];
}
const DocumentView: React.FC<DocumentProps> = ({ document, userPermissions }) => {
const hasPermission = (permission: Permission) =>
userPermissions.includes(permission);
return (
<div className="document">
<h1>{document.title}</h1>
<p>{document.content}</p>
<div className="actions">
{/* Everyone can read */}
<button>๐ View</button>
{/* Only with write permission */}
{hasPermission('write') && (
<button>โ๏ธ Edit</button>
)}
{/* Only with delete permission */}
{hasPermission('delete') && (
<button>๐๏ธ Delete</button>
)}
{/* Only admins */}
{hasPermission('admin') && (
<button>โ๏ธ Settings</button>
)}
</div>
</div>
);
};
๐๏ธ Hands-on Practice
Let's put everything together with a comprehensive exercise!
๐๏ธ Exercise: Build a Status Dashboard
Create a dashboard component that displays different states and information based on various conditions.
Requirements:
- Create a
StatusDashboardcomponent - Show loading state while data fetches
- Show error state if fetch fails
- Show empty state if no data
- Display user info when authenticated
- Show login prompt when not authenticated
- Use role-based rendering (admin/user/guest)
- Include feature flags for optional sections
Starter Code:
interface DashboardData {
user: {
name: string;
role: 'admin' | 'user' | 'guest';
isPremium: boolean;
} | null;
stats: {
posts: number;
followers: number;
following: number;
} | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
features: {
showAnalytics: boolean;
showNotifications: boolean;
};
}
const StatusDashboard: React.FC<DashboardData> = (props) => {
// Your code here!
return (
<div className="dashboard">
{/* Implement conditional rendering */}
</div>
);
};
// Test the component
const App = () => {
const [dashboardData, setDashboardData] = useState<DashboardData>({
user: null,
stats: null,
isAuthenticated: false,
isLoading: true,
error: null,
features: {
showAnalytics: true,
showNotifications: true
}
});
return <StatusDashboard {...dashboardData} />;
};
๐ก Hint 1: Structure
Start with early returns for loading and error states:
if (isLoading) return <LoadingView />;
if (error) return <ErrorView error={error} />;
if (!isAuthenticated) return <LoginPrompt />;
๐ก Hint 2: Role-Based Content
Use switch or object mapping for role-based rendering:
const roleConfig = {
admin: { icon: '๐', title: 'Admin Dashboard' },
user: { icon: '๐ค', title: 'User Dashboard' },
guest: { icon: '๐', title: 'Guest Dashboard' }
};
const config = roleConfig[user.role];
โ Solution
const StatusDashboard: React.FC<DashboardData> = ({
user,
stats,
isAuthenticated,
isLoading,
error,
features
}) => {
// Early return: Loading
if (isLoading) {
return (
<div className="dashboard loading">
<div className="spinner"></div>
<p>Loading dashboard...</p>
</div>
);
}
// Early return: Error
if (error) {
return (
<div className="dashboard error">
<h2>โ Error</h2>
<p>{error}</p>
<button>Retry</button>
</div>
);
}
// Early return: Not authenticated
if (!isAuthenticated || !user) {
return (
<div className="dashboard login-prompt">
<h2>๐ Login Required</h2>
<p>Please log in to view your dashboard.</p>
<button>Log In</button>
</div>
);
}
// Role configuration
const roleConfig = {
admin: {
icon: '๐',
title: 'Admin Dashboard',
permissions: ['view-all', 'edit-all', 'delete']
},
user: {
icon: '๐ค',
title: 'User Dashboard',
permissions: ['view-own', 'edit-own']
},
guest: {
icon: '๐',
title: 'Guest Dashboard',
permissions: ['view-limited']
}
};
const config = roleConfig[user.role];
// Main dashboard render
return (
<div className={`dashboard dashboard-${user.role}`}>
<header>
<h1>{config.icon} {config.title}</h1>
<p>Welcome back, {user.name}!</p>
{user.isPremium && <span className="badge">โญ Premium</span>}
</header>
{/* Show stats if available */}
{stats && (
<section className="stats">
<div className="stat-card">
<h3>{stats.posts}</h3>
<p>Posts</p>
</div>
<div className="stat-card">
<h3>{stats.followers}</h3>
<p>Followers</p>
</div>
<div className="stat-card">
<h3>{stats.following}</h3>
<p>Following</p>
</div>
</section>
)}
{/* Analytics (feature flag) */}
{features.showAnalytics && (
<section className="analytics">
<h2>๐ Analytics</h2>
<p>Your content performance...</p>
</section>
)}
{/* Notifications (feature flag) */}
{features.showNotifications && (
<section className="notifications">
<h2>๐ Notifications</h2>
<p>You have new updates...</p>
</section>
)}
{/* Admin-only section */}
{user.role === 'admin' && (
<section className="admin-tools">
<h2>๐ ๏ธ Admin Tools</h2>
<button>Manage Users</button>
<button>System Settings</button>
</section>
)}
{/* Premium-only features */}
{user.isPremium ? (
<section className="premium-features">
<h2>โญ Premium Features</h2>
<p>Exclusive content for premium members...</p>
</section>
) : (
<section className="upgrade-prompt">
<h2>โญ Upgrade to Premium</h2>
<p>Unlock exclusive features!</p>
<button>Upgrade Now</button>
</section>
)}
</div>
);
};
๐๏ธ Challenge Exercise: Weather App
Build a weather display component with these states:
- Loading weather data
- Location permission denied
- API error
- Different displays for sunny/rainy/cloudy/snowy
- Temperature units toggle (Celsius/Fahrenheit)
- Show/hide detailed forecast based on user preference
Bonus: Use all the conditional rendering patterns we learned!
โจ Best Practices
โ Do's
- Use early returns for edge cases and error states
- Keep conditions simple - extract complex logic into functions
- Use explicit comparisons (
> 0) instead of truthy checks when appropriate - Prefer
??over||when 0, false, or '' are valid values - Use object mapping instead of switch for configuration-based rendering
- Extract conditional logic into well-named functions for readability
- Handle all states: loading, error, empty, and success
- Use TypeScript to ensure exhaustive checks with union types
โ Don'ts
- Don't nest ternaries more than 2 levels deep - use early returns instead
- Don't use && with numbers or counts without explicit comparison
- Don't forget null/undefined checks before accessing properties
- Don't render "true" or "false" accidentally - wrap booleans properly
- Don't over-complicate - sometimes a simple if/else is best
- Don't ignore TypeScript warnings about potential nulls
- Don't render different component types in the same condition chain (breaks React's reconciliation)
Readability Guidelines
// โ Hard to read
const Component = ({ a, b, c }) => (
<div>
{a && b ? c ? <X /> : <Y /> : a || b ? <Z /> : <W />}
</div>
);
// โ
Clear and maintainable
const Component = ({ a, b, c }) => {
if (!a && !b) return <W />;
if (a || b) {
if (!a || !b) return <Z />;
return c ? <X /> : <Y />;
}
};
// โ
Even better with named helpers
const Component = ({ a, b, c }) => {
const hasBasicRequirements = a && b;
const hasAdvancedFeatures = hasBasicRequirements && c;
if (!a && !b) return <W />;
if (!hasBasicRequirements) return <Z />;
return hasAdvancedFeatures ? <X /> : <Y />;
};
Performance Considerations
๐ก Conditional Rendering and Re-renders
// โ Creates new functions on every render
const ExpensiveComponent = ({ showDetails }) => (
<div>
{showDetails && heavyComputation()} {/* Runs every render! */}
</div>
);
// โ
Compute conditionally
const ExpensiveComponent = ({ showDetails }) => {
const details = showDetails ? heavyComputation() : null;
return (
<div>
{details && <div>{details}</div>}
</div>
);
};
// โ
Or use early return
const ExpensiveComponent = ({ showDetails }) => {
if (!showDetails) {
return <div>Basic view</div>;
}
const details = heavyComputation();
return <div>{details}</div>;
};
TypeScript Exhaustiveness Checking
Ensure All Cases Are Handled
type Status = 'idle' | 'loading' | 'success' | 'error';
const StatusDisplay: React.FC<{ status: Status }> = ({ status }) => {
switch (status) {
case 'idle':
return <div>Ready</div>;
case 'loading':
return <div>Loading...</div>;
case 'success':
return <div>Success!</div>;
case 'error':
return <div>Error!</div>;
default:
// TypeScript will error if we're missing a case!
const _exhaustive: never = status;
return <div>Unknown status</div>;
}
};
๐ Summary
What You Learned
Congratulations! You now know all the essential conditional rendering patterns in React:
| Pattern | Syntax | Best For |
|---|---|---|
| If/Else | if (condition) return <A />; else return <B />; |
Simple binary conditions outside JSX |
| Ternary | {condition ? <A /> : <B />} |
Inline either/or rendering in JSX |
| && Operator | {condition && <Component />} |
Show/hide single elements |
| || Operator | {value || 'default'} |
Fallback values |
| ?? Operator | {value ?? 'default'} |
Fallback when null/undefined |
| Switch | switch(value) { case 'a': ... } |
Multiple distinct conditions |
| Early Return | if (error) return <Error />; |
Edge cases and guard clauses |
| Object Map | config[type] |
Configuration-based rendering |
๐ฏ Key Takeaways
- Choose the right tool: Each pattern has its best use case
- Readability matters: Use early returns to avoid deep nesting
- Handle all states: Loading, error, empty, and success
- Be explicit: Use
> 0instead of truthy checks for numbers - Use TypeScript: Exhaustive checking prevents missing cases
- Think about UX: Every state should have appropriate UI feedback
Common Conditional Patterns Cheatsheet
// Loading state
if (isLoading) return <Spinner />;
// Error state
if (error) return <ErrorMessage error={error} />;
// Empty state
if (items.length === 0) return <EmptyState />;
// Authenticated vs Unauthenticated
if (!isAuthenticated) return <LoginPrompt />;
// Permissions
if (!hasPermission) return <AccessDenied />;
// Feature flags
{featureEnabled && <NewFeature />}
// Null checks
{user?.profile && <ProfileCard profile={user.profile} />}
// Fallback values
<p>{username ?? 'Anonymous'}</p>
// Multiple conditions
{isVisible && !isDisabled && <Button />}
// Either/or
{isPremium ? <PremiumFeatures /> : <UpgradePrompt />}
Practice Checklist
Make sure you can:
- โ Use if/else statements for early returns
- โ Write inline ternaries in JSX
- โ Use && operator without falsy value bugs
- โ Choose between || and ?? appropriately
- โ Implement switch statements or object mapping
- โ Handle loading, error, and empty states
- โ Write clean, readable conditional code
- โ Avoid common conditional rendering pitfalls
๐ What's Next?
In the next lesson, we'll tackle the Module 3 Project, where you'll build a fully functional Todo Application that combines everything you've learned:
- useState for managing todo state
- Forms for adding new todos
- Lists for displaying todos
- Conditional rendering for loading, empty, and error states
- Filter controls (All, Active, Completed)
Get ready to put all your skills together! ๐ช