Seven Patterns for Conditional Rendering in React Applications
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 |