Conditional Rendering Patterns in React

Making Your UI Dynamic and Responsive

Overview

Welcome to the world of conditional rendering! Just as a traffic light changes based on conditions, React components can display different content based on application state. Today, you'll master various patterns for showing and hiding content, making your applications truly dynamic and responsive to user interactions and data changes.

Why Conditional Rendering?

Conditional rendering is like a choose-your-own-adventure book - different paths lead to different outcomes based on decisions. In React, we use conditions to determine what gets displayed on the screen.

graph TD A[Application State] --> B{Condition Check} B -->|True| C[Render Component A] B -->|False| D[Render Component B] C --> E[User Sees A] D --> F[User Sees B] G[User Action] --> A H[Data Loading] --> A I[Authentication] --> A style B fill:#f9f,stroke:#333,stroke-width:2px style C fill:#9cf,stroke:#333,stroke-width:2px style D fill:#9cf,stroke:#333,stroke-width:2px

Basic Conditional Rendering Patterns

1. If/Else Statements


// Using if/else before return
function Greeting({ isLoggedIn }) {
    if (isLoggedIn) {
        return <h1>Welcome back!</h1>;
    } else {
        return <h1>Please sign up.</h1>;
    }
}

// Using if/else with variables
function UserDashboard({ user }) {
    let content;
    
    if (user) {
        content = (
            <div>
                <h2>Hello, {user.name}!</h2>
                <p>Email: {user.email}</p>
            </div>
        );
    } else {
        content = <p>Please log in to see your dashboard.</p>;
    }
    
    return <div className="dashboard">{content}</div>;
}
            

2. Ternary Operator


// Simple ternary for inline conditions
function StatusBadge({ isOnline }) {
    return (
        <span className={`badge ${isOnline ? 'online' : 'offline'}`}>
            {isOnline ? 'Online' : 'Offline'}
        </span>
    );
}

// Nested ternary (use sparingly)
function UserStatus({ user }) {
    return (
        <div>
            {user ? (
                user.isAdmin ? (
                    <AdminPanel user={user} />
                ) : (
                    <UserPanel user={user} />
                )
            ) : (
                <GuestPanel />
            )}
        </div>
    );
}

// Better approach for complex conditions
function UserStatusImproved({ user }) {
    if (!user) return <GuestPanel />;
    if (user.isAdmin) return <AdminPanel user={user} />;
    return <UserPanel user={user} />;
}
            

3. Logical AND (&&) Operator


// Show component only when condition is true
function Notification({ message, show }) {
    return (
        <div>
            {show && (
                <div className="notification">
                    {message}
                </div>
            )}
        </div>
    );
}

// Multiple conditions
function UserProfile({ user, isLoading, error }) {
    return (
        <div>
            {isLoading && <LoadingSpinner />}
            {error && <ErrorMessage error={error} />}
            {user && !isLoading && !error && (
                <div>
                    <h2>{user.name}</h2>
                    <p>{user.bio}</p>
                </div>
            )}
        </div>
    );
}

// ⚠️ Watch out for falsy values!
function BadExample({ count }) {
    return (
        <div>
            {/* This will render "0" when count is 0 */}
            {count && <p>You have {count} messages</p>}
            
            {/* Better approach */}
            {count > 0 && <p>You have {count} messages</p>}
        </div>
    );
}
            

4. Logical OR (||) Operator for Defaults


// Providing default content
function WelcomeMessage({ username }) {
    return (
        <h1>Welcome, {username || 'Guest'}!</h1>
    );
}

// Using nullish coalescing (??) for better control
function UserAvatar({ avatarUrl, defaultUrl }) {
    return (
        <img 
            src={avatarUrl ?? defaultUrl} 
            alt="User avatar"
        />
    );
}
            

Advanced Conditional Patterns

1. IIFE (Immediately Invoked Function Expression)


function ComplexConditions({ user, permissions }) {
    return (
        <div>
            {(() => {
                if (!user) return <LoginPrompt />;
                if (!permissions.canView) return <AccessDenied />;
                if (permissions.isAdmin) return <AdminDashboard />;
                if (permissions.isModerator) return <ModeratorDashboard />;
                return <UserDashboard />;
            })()}
        </div>
    );
}
            

2. Switch Statements


// Using switch for multiple conditions
function NotificationIcon({ type }) {
    switch (type) {
        case 'success':
            return <SuccessIcon />;
        case 'warning':
            return <WarningIcon />;
        case 'error':
            return <ErrorIcon />;
        case 'info':
            return <InfoIcon />;
        default:
            return <DefaultIcon />;
    }
}

// Switch with render function
function renderContent(status) {
    switch (status) {
        case 'loading':
            return <LoadingSpinner />;
        case 'error':
            return <ErrorMessage />;
        case 'success':
            return <SuccessContent />;
        default:
            return null;
    }
}

function DataDisplay({ status }) {
    return (
        <div className="data-display">
            {renderContent(status)}
        </div>
    );
}
            

3. Object Mapping Pattern


// Map conditions to components
function StatusDisplay({ status }) {
    const statusComponents = {
        pending: <PendingStatus />,
        approved: <ApprovedStatus />,
        rejected: <RejectedStatus />,
        cancelled: <CancelledStatus />
    };
    
    return statusComponents[status] || <UnknownStatus />;
}

// More complex mapping with props
function DynamicComponent({ type, props }) {
    const components = {
        button: Button,
        input: Input,
        select: Select,
        textarea: TextArea
    };
    
    const Component = components[type] || DefaultComponent;
    return <Component {...props} />;
}
            

Conditional Rendering with Hooks

1. useState for UI State


function ToggleContent() {
    const [isVisible, setIsVisible] = useState(false);
    
    return (
        <div>
            <button onClick={() => setIsVisible(!isVisible)}>
                {isVisible ? 'Hide' : 'Show'} Content
            </button>
            
            {isVisible && (
                <div className="content">
                    <p>This content can be toggled</parameter>
                </div>
            )}
        </div>
    );
}
            

2. useEffect for Conditional Side Effects


function DataFetcher({ shouldFetch, query }) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(false);
    
    useEffect(() => {
        if (!shouldFetch || !query) return;
        
        setLoading(true);
        fetchData(query)
            .then(setData)
            .finally(() => setLoading(false));
    }, [shouldFetch, query]);
    
    if (!shouldFetch) {
        return <p>Data fetching is disabled</p>;
    }
    
    return loading ? <LoadingSpinner /> : <DataDisplay data={data} />;
}
            

Common Conditional Rendering Patterns

1. Loading States


function DataComponent() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        fetchData()
            .then(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, []);
    
    if (loading) {
        return (
            <div className="loading-container">
                <LoadingSpinner />
                <p>Loading data...</p>
            </div>
        );
    }
    
    if (error) {
        return (
            <div className="error-container">
                <ErrorIcon />
                <p>Error: {error.message}</p>
                <button onClick={() => window.location.reload()}>
                    Retry
                </button>
            </div>
        );
    }
    
    if (!data || data.length === 0) {
        return (
            <div className="empty-state">
                <EmptyIcon />
                <p>No data available</p>
            </div>
        );
    }
    
    return (
        <div className="data-container">
            {data.map(item => (
                <DataItem key={item.id} item={item} />
            ))}
        </div>
    );
}
            

2. Authentication-Based Rendering


// Auth wrapper component
function PrivateRoute({ children, requiredRole }) {
    const { user, loading } = useAuth();
    
    if (loading) {
        return <LoadingSpinner />;
    }
    
    if (!user) {
        return <Navigate to="/login" />;
    }
    
    if (requiredRole && user.role !== requiredRole) {
        return <AccessDenied />;
    }
    
    return children;
}

// Usage
function App() {
    return (
        <Routes>
            <Route path="/public" element={<PublicPage />} />
            <Route 
                path="/dashboard" 
                element={
                    <PrivateRoute>
                        <Dashboard />
                    </PrivateRoute>
                } 
            />
            <Route 
                path="/admin" 
                element={
                    <PrivateRoute requiredRole="admin">
                        <AdminPanel />
                    </PrivateRoute>
                } 
            />
        </Routes>
    );
}
            

3. Feature Flags


// Feature flag context
const FeatureFlagContext = createContext({});

function FeatureFlagProvider({ children }) {
    const [features, setFeatures] = useState({
        newDashboard: false,
        betaFeatures: false,
        advancedAnalytics: true
    });
    
    return (
        <FeatureFlagContext.Provider value={features}>
            {children}
        </FeatureFlagContext.Provider>
    );
}

// Feature flag hook
function useFeature(featureName) {
    const features = useContext(FeatureFlagContext);
    return features[featureName] || false;
}

// Using feature flags
function Dashboard() {
    const hasNewDashboard = useFeature('newDashboard');
    
    return hasNewDashboard ? <NewDashboard /> : <LegacyDashboard />;
}
            

4. Responsive Rendering


// Media query hook
function useMediaQuery(query) {
    const [matches, setMatches] = useState(false);
    
    useEffect(() => {
        const media = window.matchMedia(query);
        if (media.matches !== matches) {
            setMatches(media.matches);
        }
        
        const listener = () => setMatches(media.matches);
        media.addEventListener('change', listener);
        
        return () => media.removeEventListener('change', listener);
    }, [matches, query]);
    
    return matches;
}

// Responsive component
function ResponsiveLayout() {
    const isMobile = useMediaQuery('(max-width: 768px)');
    const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
    
    if (isMobile) {
        return <MobileLayout />;
    }
    
    if (isTablet) {
        return <TabletLayout />;
    }
    
    return <DesktopLayout />;
}
            

Performance Considerations

1. Conditional Component Mounting


// Component is unmounted when condition is false
function ConditionalMount({ show, children }) {
    return show ? children : null;
}

// Component is hidden but still mounted
function ConditionalDisplay({ show, children }) {
    return (
        <div style={{ display: show ? 'block' : 'none' }}>
            {children}
        </div>
    );
}

// When to use each:
// - ConditionalMount: For expensive components
// - ConditionalDisplay: For frequently toggled content
            

2. Memoizing Conditional Components


const ExpensiveComponent = React.memo(({ data }) => {
    console.log('Expensive component rendered');
    // Complex rendering logic
    return <div>{/* ... */}</div>;
});

function OptimizedConditional({ showExpensive, data }) {
    const memoizedComponent = useMemo(() => {
        return showExpensive ? <ExpensiveComponent data={data} /> : null;
    }, [showExpensive, data]);
    
    return (
        <div>
            {memoizedComponent}
        </div>
    );
}
            

Best Practices

graph TD A[Conditional Rendering Best Practices] --> B[Keep it Simple] A --> C[Extract Complex Logic] A --> D[Handle Edge Cases] A --> E[Optimize Performance] B --> F[Prefer ternary for simple cases] B --> G[Use && for show/hide] C --> H[Custom hooks] C --> I[Separate functions] D --> J[Loading states] D --> K[Error states] D --> L[Empty states] E --> M[Lazy loading] E --> N[Memoization] style A fill:#f9f,stroke:#333,stroke-width:2px

Anti-patterns to Avoid


// ❌ Deeply nested ternaries
function BadNesting({ user, permissions, settings }) {
    return (
        <div>
            {user ? (
                permissions ? (
                    settings.enabled ? (
                        <Dashboard />
                    ) : (
                        <DisabledMessage />
                    )
                ) : (
                    <NoPermission />
                )
            ) : (
                <LoginPrompt />
            )}
        </div>
    );
}

// ✅ Better approach
function GoodStructure({ user, permissions, settings }) {
    if (!user) return <LoginPrompt />;
    if (!permissions) return <NoPermission />;
    if (!settings.enabled) return <DisabledMessage />;
    return <Dashboard />;
}

// ❌ Rendering numbers directly
function BadNumberRendering({ count }) {
    // This renders "0" when count is 0
    return <div>{count && `You have ${count} items`}</div>;
}

// ✅ Better approach
function GoodNumberRendering({ count }) {
    return <div>{count > 0 && `You have ${count} items`}</div>;
}
            

Practice Exercises

Exercise 1: Multi-State Component

Create a component with multiple conditional states:


function MultiStateComponent() {
    // Create a component that:
    // - Shows loading spinner while fetching
    // - Shows error message on failure
    // - Shows empty state if no data
    // - Shows data when available
    // - Handles retry functionality
}
            

Exercise 2: Role-Based UI

Build a role-based dashboard system:


function RoleBasedDashboard() {
    // Create a component that:
    // - Shows different UI for admin/user/guest
    // - Handles permission checks
    // - Provides fallback for unauthorized access
    // - Implements feature flags
}
            

Exercise 3: Dynamic Form

Create a form with conditional fields:


function DynamicForm() {
    // Create a form that:
    // - Shows/hides fields based on other field values
    // - Validates conditionally required fields
    // - Displays different submit buttons based on state
    // - Handles multi-step conditional flow
}
            

Key Takeaways

What's Next?

Tomorrow, we'll dive into Component Composition patterns. You'll learn how to build complex UIs by combining simple components, creating flexible and reusable component hierarchies!

Homework