Skip to main content

๐Ÿ”€ 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

graph TD A["๐Ÿ”€ Conditional Rendering"] --> B["๐Ÿ“ if/else statements"] A --> C["โ“ Ternary operator ?:"] A --> D["โž• Logical && operator"] A --> E["๐Ÿ”„ Logical || operator"] A --> F["๐Ÿ”€ Switch statements"] A --> G["โ†ฉ๏ธ Early returns"] A --> H["๐Ÿ“ฆ Element variables"] B --> B1["Best for: Complex logic"] C --> C1["Best for: Inline either/or"] D --> D1["Best for: Show/hide"] E --> E1["Best for: Fallback values"] F --> F1["Best for: Multiple cases"] G --> G1["Best for: Guard clauses"] H --> H1["Best for: Pre-calculated JSX"] style A fill:#667eea,stroke:#5a67d8,color:#fff style B fill:#e3f2fd,stroke:#2196F3 style C fill:#e3f2fd,stroke:#2196F3 style D fill:#e3f2fd,stroke:#2196F3 style E fill:#e3f2fd,stroke:#2196F3 style F fill:#e3f2fd,stroke:#2196F3 style G fill:#e3f2fd,stroke:#2196F3 style H fill:#e3f2fd,stroke:#2196F3 style B1 fill:#e8f5e9,stroke:#4CAF50 style C1 fill:#e8f5e9,stroke:#4CAF50 style D1 fill:#e8f5e9,stroke:#4CAF50 style E1 fill:#e8f5e9,stroke:#4CAF50 style F1 fill:#e8f5e9,stroke:#4CAF50 style G1 fill:#e8f5e9,stroke:#4CAF50 style H1 fill:#e8f5e9,stroke:#4CAF50

๐ŸŽฏ Interactive: Which Pattern Should You Use?

Click through this decision tree to find the best conditional rendering pattern for your use case:

What do you need? Either/Or Choice Show/Hide One Thing Multiple Cases Default Value Inline JSX? Complex logic? โœ… Ternary condition ? a : b โœ… If/Else Early returns โœ… && Operator condition && jsx โœ… Switch/Map config[type] โœ… || or ?? value ?? default Click a node above to explore! Start by clicking "What do you need?"

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:

isLoggedIn ? โŒฉDashboard/โŒช : โŒฉLogin/โŒช true โ†’ Returns first option โŒฉDashboard /โŒช
Rendered Output:

โž• 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!

3
โŒ 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:

โŒ Nested Conditionals โœ… Early Returns return ( {user ? ( user.isPremium ? ( user.hasProfile ? ( ๐Ÿ˜ต Main Content (buried deep!) ) : <ProfilePrompt /> ) : <UpgradePrompt /> ) : <LoginPrompt />} ); 4 levels deep ๐Ÿ˜ฐ if (!user) return <LoginPrompt />; Guard โ‘  if (!user.isPremium) return <UpgradePrompt />; Guard โ‘ก if (!user.hasProfile) return <ProfilePrompt />; Guard โ‘ข // Happy path - no nesting! return ( <div> <h1>{user.name}</h1> </div> ๐Ÿ˜Š Main Content All at the same level! ๐ŸŽ‰

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:

  1. Create a StatusDashboard component
  2. Show loading state while data fetches
  3. Show error state if fetch fails
  4. Show empty state if no data
  5. Display user info when authenticated
  6. Show login prompt when not authenticated
  7. Use role-based rendering (admin/user/guest)
  8. 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 > 0 instead 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! ๐Ÿ’ช