Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Seven Patterns for Conditional Rendering in React Applications

Notes May 14 2

Conditional rendering represents one of the most fundamental patterns in React development, enabling components to adapt their output based on application state, user permissions, or runtime data. Unlike template-based frameworks that provide specialized syntax, React leverages JavaScript's native control flow within JSX, offering flexibility but requiring careful pattern selection.

1. External If/Else Blocks for Complex Branching

When components require substantial logic before rendering, extracting conditional logic outside the return statement produces cleaner code. This approach separates data preparation from presentation.

import { useState } from 'react';

function AuthStatus() {
  const [session, setSession] = useState(null);
  
  if (!session) {
    return (
      <div className="auth-prompt">
        <h2>Access Required</h2>
        <button onClick={() => setSession({ user: 'Alex' })}>
          Sign In
        </button>
      </div>
    );
  }
  
  return (
    <div className="user-dashboard">
      <h2>Welcome, {session.user}</h2>
      <button onClick={() => setSession(null)}>Logout</button>
    </div>
  );
}

Alternatively, assign JSX to variables when multiple branches share layout wrappers:

function ContentPreview() {
  const [content, setContent] = useState([]);
  let display;
  
  if (content.length === 0) {
    display = <EmptyState icon="inbox" message="No items found" />;
  } else {
    display = <ItemGrid data={content} />;
  }
  
  return (
    <section className="container">
      <header>Your Content</header>
      {display}
    </section>
  );
}

2. Inline Ternary Expressions for Binary Decisions

For straightforward either/or scenarios, the ternary operator embeds directly within JSX expressions, eliminating the need for variable declarations.

function ConnectivityDot() {
  const [latency, setLatency] = useState(45);
  
  return (
    <div className="status-bar">
      <span className="indicator" style={{
        backgroundColor: latency < 100 ? '#22c55e' : '#ef4444'
      }} />
      <span>{latency < 100 ? 'Optimal Connection' : 'High Latency'}</span>
    </div>
  );
}

Caution: Avoid chaining multiple ternary operators. When logic exceeds one condition, refactor to element variables or dedicated functions to maintain readability.

3. Short-Circuit Evaluation for Conditional Inclusion

The logical AND operator (&&) provides an elegant syntax for rendering elements only when a condition evaluates truthy, particularly useful for badges, notifications, and optional UI elements.

function NotificationBell() {
  const [alerts, setAlerts] = useState(5);
  
  return (
    <button className="toolbar-btn" aria-label="Notifications">
      <BellIcon />
      {alerts > 0 && (
        <span className="badge" aria-label={`${alerts} unread`}>
          {alerts}
        </span>
      )}
    </button>
  );
}

Critical Edge Case: Numeric values require explicit comparison. The expression {count && <span>{count}</span>} renders 0 when count equals zero because JavaScript treats 0 as falsy but React still renders it. Always use {count > 0 && ...} or {Boolean(count) && ...}.

4. Element Variables for Multi-State Scenarios

When handling three or more distinct states, storing JSX in variables prevents deeply nested conditionals and improves component scanability.

function AccountTier() {
  const [tier, setTier] = useState('basic');
  
  let upgradePrompt;
  if (tier === 'enterprise') {
    upgradePrompt = (
      <div className="tier-card premium">
        <h3>Enterprise Suite</h3>
        <p>Dedicated support and unlimited bandwidth</p>
      </div>
    );
  } else if (tier === 'pro') {
    upgradePrompt = (
      <div className="tier-card standard">
        <h3>Professional</h3>
        <button>Upgrade to Enterprise</button>
      </div>
    );
  } else {
    upgradePrompt = (
      <div className="tier-card free">
        <h3>Basic Plan</h3>
        <button>Unlock Pro Features</button>
      </div>
    );
  }
  
  return (
    <aside className="sidebar">
      {upgradePrompt}
    </aside>
  );
}

5. Switch Statements for Enumerated States

When conditions involve specific string literals or constants (such as API status codes or workflow stages), switch statements offer clearer intent than if-else chains and enforce explicit handling of all cases through the default branch.

function SyncStatus() {
  const [phase, setPhase] = useState('uploading');
  
  const renderState = () => {
    switch (phase) {
      case 'idle':
        return <p className="text-gray">Ready to synchronize</p>;
      case 'uploading':
        return <ProgressBar percent={33} label="Uploading data..." />;
      case 'processing':
        return <ProgressBar percent={66} label="Processing records..." />;
      case 'complete':
        return <p className="text-green">Synchronization complete ✓</p>;
      case 'error':
        return (
          <div className="alert error">
            <p>Connection failed</p>
            <button onClick={retry}>Retry</button>
          </div>
        );
      default:
        return <p>Unknown status</p>;
    }
  };
  
  return (
    <div className="sync-panel">
      <h4>Cloud Sync</h4>
      {renderState()}
    </div>
  );
}

6. Permission Gate Components for Reusable Access Control

Encapsulate frequently reused conditional logic—particularly authorization checks—in to specialized wrapper components that accept children and optional fallback props.

function FeatureGate({ requiredRole, children, fallback = null }) {
  const userRole = useAuth(); // Custom hook retrieving current role
  
  const hasAccess = 
    requiredRole === 'guest' || 
    (requiredRole === 'user' && ['user', 'admin'].includes(userRole)) ||
    (requiredRole === 'admin' && userRole === 'admin');
    
  return hasAccess ? children : fallback;
}

// Implementation
function AdminConsole() {
  return (
    <div className="dashboard">
      <h1>System Administration</h1>
      
      <FeatureGate 
        requiredRole="admin" 
        fallback={<p>Administrator access required</p>}
      >
        <UserManagementTable />
        <SecuritySettings />
      </FeatureGate>
      
      <FeatureGate requiredRole="user">
        <ActivityLog readonly />
      </FeatureGate>
    </div>
  );
}

This pattern centralizes authorization logic, simplifying maintenance when permission rules evolve.

7. Code Splitting with Lazy Loading and Suspense

For performance-critical applications, conditionally import heavy components only when needed, combining dynamic imports with React's Suspense boundary to display loading states during network retrieval.

import { Suspense, useState, lazy } from 'react';

const AnalyticsDashboard = lazy(() => import('./AnalyticsDashboard'));
const SimpleMetrics = lazy(() => import('./SimpleMetrics'));

function ReportingView() {
  const [advancedMode, setAdvancedMode] = useState(false);
  
  return (
    <div className="report-container">
      <header>
        <h2>Performance Metrics</h2>
        <label>
          <input 
            type="checkbox" 
            checked={advancedMode}
            onChange={(e) => setAdvancedMode(e.target.checked)}
          />
          Advanced Analytics
        </label>
      </header>
      
      <Suspense fallback={<SkeletonLoader rows={5} />}>
        {advancedMode ? (
          <AnalyticsDashboard timeRange="30d" />
        ) : (
          <SimpleMetrics />
        )}
      </Suspense>
    </div>
  );
}

This technique reduces initial bundle size significantly, particularly effective for admin panels, chart libraries, or rich text editors that only load for specific user interactions.

Common Implementation Pitfalls

Falsy Value Rendering React renders 0, empty strings '', and NaN as text nodes. Always convert numeric checks to boolean:

// Risky: renders "0" when cart is empty
{cartItems.length && <CartPreview />}

// Safe: explicitly checks boolean condition
{cartItems.length > 0 && <CartPreview />}

Undefined Returns Ensure all code paths return valid JSX. Functions returning undefined implicitly will trigger runtime errors in React 18's strict mode.

Over-Nesting When conditional logic exceeds two levels, extract into sub-components or helper functions rather than nesting ternaries:

// Avoid
{isActive ? (hasData ? <Chart /> : <Empty />) : <Disabled />}

// Prefer
<StatusWrapper isActive={isActive} hasData={hasData} />

Pattern Selection Reference

Technique Best For Trade-offs
If/Else External Complex logic, multiple hooks Verbose for simple cases
Ternary Operator Binary choices, inline styling Poor readability when nested
Logical AND Optional single elements Risk of rendering 0 or ""
Element Variables 3+ branches, shared containers Requires variable declaration
Switch Statement State machines, status codes Slightly more boilerplate
Gate Components Reusable auth/feature flags Additional abstraction layer
Dynamic Import Heavy conditional components Async complexity, loading states

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.