AdSense Leaderboard

Google AdSense Placeholder

HEADER SLOT

Conditional Rendering Best Practices: Pattern Guide for React 19

Last updated:
Controlled vs Uncontrolled: React Form State Patterns

Master conditional rendering in React 19. Learn optimal patterns: ternary operators, logical AND, early returns, and how to avoid common pitfalls with production-ready examples.

# Conditional Rendering Best Practices: Pattern Guide for React 19

Conditional rendering is one of the first techniques you learn in React, but mastering it — choosing the right pattern for each situation — separates junior developers from experienced ones. A single poor conditional rendering choice can cascade into unmaintainable code, unnecessary re-renders, or subtle bugs that only appear in production.

In this guide, I'll show you the five main conditional rendering patterns used in production React applications, when to use each one, and the common pitfalls that trip up developers.

# Table of Contents

  1. Pattern Comparison Table
  2. Method 1: Early Return Pattern
  3. Method 2: Ternary Operator
  4. Method 3: Logical AND (&&)
  5. Method 4: Enum Objects & Dynamic Selection
  6. Method 5: Separate Component Variables
  7. Common Pitfalls & Solutions
  8. Decision Framework
  9. Practical Scenarios
  10. FAQ

# Pattern Comparison Table {#patterns-table}

Pattern Use Case Readability Performance Risk Level
Early Return Simple true/false checks Excellent Best Low
Ternary Operator Two branches, simple logic Good Good Medium
Logical AND (&&) Show/hide single elements Good Good Medium-High
Enum Objects Multiple discrete states Excellent Best Low
Component Variables Complex multi-line content Good Good Low

# Method 1: Early Return Pattern {#early-return}

The early return pattern is the gold standard for simple conditional rendering. It's the most readable, most performant, and least error-prone approach.

# When to Use

  • Component should render nothing or something drastically different
  • Simple true/false conditions (not "show this OR that")
  • Checking permissions, loading states, or authentication

# TypeScript Implementation

typescript
import { ReactNode } from 'react';

interface ProtectedComponentProps {
  isAuthorized: boolean;
  children: ReactNode;
}

export function ProtectedComponent({ isAuthorized, children }: ProtectedComponentProps) {
  // ✅ Early return: If unauthorized, render nothing
  if (!isAuthorized) {
    return null;
  }

  return (
    <div className="protected-content">
      {children}
    </div>
  );
}

interface DataDisplayProps {
  data: string[] | null;
  isLoading: boolean;
  error: Error | null;
}

export function DataDisplay({ data, isLoading, error }: DataDisplayProps) {
  // ✅ Check loading state first
  if (isLoading) {
    return <div className="spinner">Loading...</div>;
  }

  // ✅ Check error state next
  if (error) {
    return (
      <div className="error" role="alert">
        <p>Error: {error.message}</p>
      </div>
    );
  }

  // ✅ Check data existence
  if (!data || data.length === 0) {
    return <p className="empty-state">No data available</p>;
  }

  // ✅ Happy path — render main content
  return (
    <ul className="data-list">
      {data.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

# JavaScript Implementation

javascript
export function ProtectedComponent({ isAuthorized, children }) {
  if (!isAuthorized) {
    return null;
  }

  return (
    <div className="protected-content">
      {children}
    </div>
  );
}

export function DataDisplay({ data, isLoading, error }) {
  if (isLoading) {
    return <div className="spinner">Loading...</div>;
  }

  if (error) {
    return (
      <div className="error" role="alert">
        <p>Error: {error.message}</p>
      </div>
    );
  }

  if (!data || data.length === 0) {
    return <p className="empty-state">No data available</p>;
  }

  return (
    <ul className="data-list">
      {data.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

# Why This Pattern Is Superior

  • Readable: Each condition is clear and obvious
  • Maintainable: Easy to add new conditions without nesting
  • Debuggable: You can set breakpoints on each condition
  • Flat control flow: No nested conditionals to parse mentally
  • Follows "happy path" principle: Happy path is always at the end

# Method 2: Ternary Operator {#ternary-operator}

The ternary operator is perfect when you have exactly two branches and simple logic. It's concise and works well inline.

# When to Use

  • Exactly two possible outcomes (render A or render B)
  • Both branches are simple (text, icons, or small components)
  • Not deeply nested ternary operators

# TypeScript Implementation

typescript
import { ReactNode } from 'react';

interface UserGreetingProps {
  userName: string | null;
  isNewUser: boolean;
}

export function UserGreeting({ userName, isNewUser }: UserGreetingProps) {
  return (
    <div className="greeting">
      {userName ? (
        <h1>Welcome back, {userName}!</h1>
      ) : (
        <h1>Welcome to Our App</h1>
      )}

      <p>
        {isNewUser
          ? 'Check out our getting started guide'
          : 'View your dashboard'}
      </p>
    </div>
  );
}

interface ButtonProps {
  isSubmitting: boolean;
  children: ReactNode;
}

export function SubmitButton({ isSubmitting, children }: ButtonProps) {
  return (
    <button
      disabled={isSubmitting}
      className={isSubmitting ? 'btn-loading' : 'btn-primary'}
      aria-busy={isSubmitting}
    >
      {isSubmitting ? 'Submitting...' : children}
    </button>
  );
}

interface StatusBadgeProps {
  status: 'active' | 'inactive' | 'pending';
}

export function StatusBadge({ status }: StatusBadgeProps) {
  // ✅ Good: Simple ternary for two outcomes
  return (
    <span className={`badge badge-${status}`}>
      {status === 'active' ? '✓ Active' : '○ Inactive'}
    </span>
  );
}

# JavaScript Implementation

javascript
export function UserGreeting({ userName, isNewUser }) {
  return (
    <div className="greeting">
      {userName ? (
        <h1>Welcome back, {userName}!</h1>
      ) : (
        <h1>Welcome to Our App</h1>
      )}

      <p>
        {isNewUser
          ? 'Check out our getting started guide'
          : 'View your dashboard'}
      </p>
    </div>
  );
}

export function SubmitButton({ isSubmitting, children }) {
  return (
    <button
      disabled={isSubmitting}
      className={isSubmitting ? 'btn-loading' : 'btn-primary'}
      aria-busy={isSubmitting}
    >
      {isSubmitting ? 'Submitting...' : children}
    </button>
  );
}

export function StatusBadge({ status }) {
  return (
    <span className={`badge badge-${status}`}>
      {status === 'active' ? '✓ Active' : '○ Inactive'}
    </span>
  );
}

# When NOT to Nest Ternary Operators

typescript
// ❌ AVOID: Nested ternary operators are hard to read
const message = userRole === 'admin' 
  ? adminCanDelete 
    ? 'Delete this item?' 
    : 'Contact support to delete'
  : userRole === 'moderator'
    ? 'Report for review'
    : 'Contact support';

// ✅ BETTER: Use early returns or separate logic
function getDeleteMessage(userRole: string, adminCanDelete: boolean): string {
  if (userRole === 'admin') {
    return adminCanDelete ? 'Delete this item?' : 'Contact support to delete';
  }
  if (userRole === 'moderator') {
    return 'Report for review';
  }
  return 'Contact support';
}

# Method 3: Logical AND (&&) {#logical-and}

The logical AND pattern is concise for rendering optional elements. However, it comes with a subtle trap that catches many developers.

# When to Use

  • Rendering an element only if a condition is true
  • No "else" branch needed (null is acceptable)
  • Simple, short inline checks

# TypeScript Implementation (Correct Approach)

typescript
import { ReactNode } from 'react';

interface AlertProps {
  isVisible: boolean;
  children: ReactNode;
}

export function Alert({ isVisible, children }: AlertProps) {
  // ✅ GOOD: Boolean condition with single element
  return (
    <div>
      {isVisible && (
        <div className="alert" role="alert">
          {children}
        </div>
      )}
    </div>
  );
}

interface NotificationBadgeProps {
  unreadCount: number;
}

export function NotificationBadge({ unreadCount }: NotificationBadgeProps) {
  // ✅ GOOD: Boolean condition (unreadCount > 0 returns true/false)
  return (
    <div className="notification">
      {unreadCount > 0 && (
        <span className="badge">{unreadCount}</span>
      )}
    </div>
  );
}

interface FormErrorProps {
  fieldName: string;
  errors: Record<string, string> | null;
}

export function FormError({ fieldName, errors }: FormErrorProps) {
  // ✅ CORRECT: Convert to boolean to avoid rendering falsy values
  return (
    <div>
      {!!errors?.[fieldName] && (
        <p className="error" role="alert">
          {errors[fieldName]}
        </p>
      )}
    </div>
  );
}

interface FeatureProps {
  isFeatureEnabled: boolean;
  featureName: string;
}

export function FeatureToggle({ isFeatureEnabled, featureName }: FeatureProps) {
  // ✅ CORRECT: Wrapping complex condition in parentheses
  return (
    <section>
      {isFeatureEnabled && (
        <div className="feature">
          <h2>{featureName}</h2>
          <p>This feature is now available</p>
        </div>
      )}
    </section>
  );
}

# Common Pitfall: Rendering Falsy Values

typescript
// ❌ DANGEROUS: This will render "0" on screen!
interface CounterProps {
  count: number;
}

export function Counter({ count }: CounterProps) {
  return (
    <div>
      {count && <p>You have {count} items</p>}
      {/* If count is 0, this renders: 0 (the number zero appears on page!) */}
    </div>
  );
}

// ✅ FIX 1: Use boolean comparison
export function CounterFixed1({ count }: CounterProps) {
  return (
    <div>
      {count > 0 && <p>You have {count} items</p>}
    </div>
  );
}

// ✅ FIX 2: Convert to boolean with !!
export function CounterFixed2({ count }: CounterProps) {
  return (
    <div>
      {!!count && <p>You have {count} items</p>}
    </div>
  );
}

// ✅ FIX 3: Use ternary with explicit null
export function CounterFixed3({ count }: CounterProps) {
  return (
    <div>
      {count ? <p>You have {count} items</p> : null}
    </div>
  );
}

# JavaScript Implementation (Correct)

javascript
export function Alert({ isVisible, children }) {
  return (
    <div>
      {isVisible && (
        <div className="alert" role="alert">
          {children}
        </div>
      )}
    </div>
  );
}

export function NotificationBadge({ unreadCount }) {
  return (
    <div className="notification">
      {unreadCount > 0 && (
        <span className="badge">{unreadCount}</span>
      )}
    </div>
  );
}

export function FormError({ fieldName, errors }) {
  return (
    <div>
      {!!errors?.[fieldName] && (
        <p className="error" role="alert">
          {errors[fieldName]}
        </p>
      )}
    </div>
  );
}

# Method 4: Enum Objects & Dynamic Selection {#enum-objects}

For multiple discrete states (not just true/false), enum objects provide elegant, maintainable code. This pattern scales beautifully as complexity grows.

# When to Use

  • Multiple fixed states (3+ outcomes)
  • Mapping state values to UI elements
  • Ensuring all states are handled
  • Need type-safety for state values

# TypeScript Implementation

typescript
import { ReactNode } from 'react';

// ✅ Type-safe state definition
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';

interface OrderStatusProps {
  status: OrderStatus;
}

// ✅ Map status to UI content using enum pattern
export function OrderStatus({ status }: OrderStatusProps) {
  const statusMessages: Record<OrderStatus, ReactNode> = {
    pending: (
      <>
        <span className="icon-pending">⏳</span>
        <span>Order Pending</span>
      </>
    ),
    processing: (
      <>
        <span className="icon-processing">🔄</span>
        <span>Processing Order</span>
      </>
    ),
    completed: (
      <>
        <span className="icon-completed">✓</span>
        <span>Order Completed</span>
      </>
    ),
    cancelled: (
      <>
        <span className="icon-cancelled">✗</span>
        <span>Order Cancelled</span>
      </>
    ),
  };

  return (
    <div className={`status status-${status}`}>
      {statusMessages[status]}
    </div>
  );
}

// ✅ More complex example with functions
type NotificationLevel = 'success' | 'warning' | 'error' | 'info';

interface NotificationProps {
  level: NotificationLevel;
  message: string;
}

export function Notification({ level, message }: NotificationProps) {
  // Map level to configuration including icons, colors, etc.
  const levelConfig: Record<NotificationLevel, {
    icon: string;
    backgroundColor: string;
    borderColor: string;
    role: string;
  }> = {
    success: {
      icon: '✓',
      backgroundColor: 'bg-green-50',
      borderColor: 'border-green-300',
      role: 'status',
    },
    warning: {
      icon: '⚠',
      backgroundColor: 'bg-yellow-50',
      borderColor: 'border-yellow-300',
      role: 'alert',
    },
    error: {
      icon: '✗',
      backgroundColor: 'bg-red-50',
      borderColor: 'border-red-300',
      role: 'alert',
    },
    info: {
      icon: 'ℹ',
      backgroundColor: 'bg-blue-50',
      borderColor: 'border-blue-300',
      role: 'status',
    },
  };

  const config = levelConfig[level];

  return (
    <div
      className={`notification ${config.backgroundColor} ${config.borderColor}`}
      role={config.role}
    >
      <span className="icon">{config.icon}</span>
      <span className="message">{message}</span>
    </div>
  );
}

// ✅ Using enum object for conditional rendering
type FormFieldType = 'text' | 'email' | 'password' | 'textarea';

interface FormFieldProps {
  type: FormFieldType;
  label: string;
  value: string;
  onChange: (value: string) => void;
}

export function FormField({ type, label, value, onChange }: FormFieldProps) {
  // Map field type to component
  const fieldComponents: Record<FormFieldType, ReactNode> = {
    text: (
      <input
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
    email: (
      <input
        type="email"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
    password: (
      <input
        type="password"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
    textarea: (
      <textarea
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    ),
  };

  return (
    <label>
      {label}
      {fieldComponents[type]}
    </label>
  );
}

# JavaScript Implementation

javascript
export function OrderStatus({ status }) {
  const statusMessages = {
    pending: (
      <>
        <span className="icon-pending"></span>
        <span>Order Pending</span>
      </>
    ),
    processing: (
      <>
        <span className="icon-processing">🔄</span>
        <span>Processing Order</span>
      </>
    ),
    completed: (
      <>
        <span className="icon-completed"></span>
        <span>Order Completed</span>
      </>
    ),
    cancelled: (
      <>
        <span className="icon-cancelled"></span>
        <span>Order Cancelled</span>
      </>
    ),
  };

  return (
    <div className={`status status-${status}`}>
      {statusMessages[status]}
    </div>
  );
}

export function Notification({ level, message }) {
  const levelConfig = {
    success: {
      icon: '✓',
      backgroundColor: 'bg-green-50',
      borderColor: 'border-green-300',
      role: 'status',
    },
    warning: {
      icon: '⚠',
      backgroundColor: 'bg-yellow-50',
      borderColor: 'border-yellow-300',
      role: 'alert',
    },
    error: {
      icon: '✗',
      backgroundColor: 'bg-red-50',
      borderColor: 'border-red-300',
      role: 'alert',
    },
    info: {
      icon: 'ℹ',
      backgroundColor: 'bg-blue-50',
      borderColor: 'border-blue-300',
      role: 'status',
    },
  };

  const config = levelConfig[level];

  return (
    <div
      className={`notification ${config.backgroundColor} ${config.borderColor}`}
      role={config.role}
    >
      <span className="icon">{config.icon}</span>
      <span className="message">{message}</span>
    </div>
  );
}

# Method 5: Separate Component Variables {#component-variables}

For complex, multi-line conditional content, storing JSX in variables makes code more readable than inline conditionals.

# When to Use

  • Conditional content is multiple lines
  • Content is complex (nested components, lots of JSX)
  • You want to compute the content separately from rendering
  • Improves readability when condition is complex

# TypeScript Implementation

typescript
import { ReactNode } from 'react';

interface UserProfileProps {
  userName: string | null;
  userRole: 'admin' | 'user' | 'guest';
  isPremium: boolean;
  subscriptionDaysLeft: number;
}

export function UserProfile({
  userName,
  userRole,
  isPremium,
  subscriptionDaysLeft,
}: UserProfileProps) {
  // ✅ Separate complex conditional content
  let roleIndicator: ReactNode;

  if (userRole === 'admin') {
    roleIndicator = (
      <span className="role-badge admin">
        <span className="icon">👑</span> Administrator
      </span>
    );
  } else if (userRole === 'user') {
    roleIndicator = <span className="role-badge user">User</span>;
  } else {
    roleIndicator = <span className="role-badge guest">Guest</span>;
  }

  // ✅ Another conditional block for premium status
  let subscriptionStatus: ReactNode;

  if (isPremium) {
    subscriptionStatus =
      subscriptionDaysLeft > 30 ? (
        <p className="subscription-active">
          Premium active (expires in {subscriptionDaysLeft} days)
        </p>
      ) : subscriptionDaysLeft > 0 ? (
        <p className="subscription-warning">
          Premium renews in {subscriptionDaysLeft} days
        </p>
      ) : (
        <p className="subscription-expired">Premium subscription expired</p>
      );
  } else {
    subscriptionStatus = (
      <p>
        <a href="/upgrade">Upgrade to Premium</a>
      </p>
    );
  }

  return (
    <div className="user-profile">
      <h1>Welcome, {userName || 'Guest'}</h1>
      {roleIndicator}
      {subscriptionStatus}
    </div>
  );
}

interface ReviewListProps {
  reviews: Review[] | null;
  isLoading: boolean;
  error: Error | null;
  onLoadMore: () => void;
}

interface Review {
  id: string;
  author: string;
  rating: number;
  text: string;
}

export function ReviewList({
  reviews,
  isLoading,
  error,
  onLoadMore,
}: ReviewListProps) {
  // ✅ Separate content rendering logic
  let content: ReactNode;

  if (isLoading) {
    content = (
      <div className="loading-state" aria-busy="true">
        <div className="spinner" />
        <p>Loading reviews...</p>
      </div>
    );
  } else if (error) {
    content = (
      <div className="error-state" role="alert">
        <p>Failed to load reviews: {error.message}</p>
        <button onClick={onLoadMore}>Try Again</button>
      </div>
    );
  } else if (!reviews || reviews.length === 0) {
    content = (
      <div className="empty-state">
        <p>No reviews yet. Be the first to review!</p>
      </div>
    );
  } else {
    content = (
      <div className="reviews-container">
        {reviews.map((review) => (
          <article key={review.id} className="review">
            <h3>{review.author}</h3>
            <div className="rating">
              {'★'.repeat(review.rating)}{'☆'.repeat(5 - review.rating)}
            </div>
            <p>{review.text}</p>
          </article>
        ))}
        <button onClick={onLoadMore} className="load-more">
          Load More Reviews
        </button>
      </div>
    );
  }

  return (
    <section className="reviews">
      <h2>Customer Reviews</h2>
      {content}
    </section>
  );
}

# JavaScript Implementation

javascript
export function UserProfile({
  userName,
  userRole,
  isPremium,
  subscriptionDaysLeft,
}) {
  let roleIndicator;

  if (userRole === 'admin') {
    roleIndicator = (
      <span className="role-badge admin">
        <span className="icon">👑</span> Administrator
      </span>
    );
  } else if (userRole === 'user') {
    roleIndicator = <span className="role-badge user">User</span>;
  } else {
    roleIndicator = <span className="role-badge guest">Guest</span>;
  }

  let subscriptionStatus;

  if (isPremium) {
    subscriptionStatus =
      subscriptionDaysLeft > 30 ? (
        <p className="subscription-active">
          Premium active (expires in {subscriptionDaysLeft} days)
        </p>
      ) : subscriptionDaysLeft > 0 ? (
        <p className="subscription-warning">
          Premium renews in {subscriptionDaysLeft} days
        </p>
      ) : (
        <p className="subscription-expired">Premium subscription expired</p>
      );
  } else {
    subscriptionStatus = (
      <p>
        <a href="/upgrade">Upgrade to Premium</a>
      </p>
    );
  }

  return (
    <div className="user-profile">
      <h1>Welcome, {userName || 'Guest'}</h1>
      {roleIndicator}
      {subscriptionStatus}
    </div>
  );
}

# Common Pitfalls & Solutions {#pitfalls}

# Pitfall 1: Rendering Falsy Values Accidentally

typescript
// ❌ WRONG: Will render "0" or empty string on page
interface CartProps {
  itemCount: number;
}

function Cart({ itemCount }: CartProps) {
  return <div>{itemCount && <span>Items: {itemCount}</span>}</div>;
  // If itemCount = 0, renders: 0
}

// ✅ Solution 1: Boolean comparison
function CartFixed1({ itemCount }: CartProps) {
  return <div>{itemCount > 0 && <span>Items: {itemCount}</span>}</div>;
}

// ✅ Solution 2: Convert to boolean
function CartFixed2({ itemCount }: CartProps) {
  return <div>{!!itemCount && <span>Items: {itemCount}</span>}</div>;
}

// ✅ Solution 3: Use ternary
function CartFixed3({ itemCount }: CartProps) {
  return (
    <div>{itemCount ? <span>Items: {itemCount}</span> : null}</div>
  );
}

# Pitfall 2: Deeply Nested Ternary Expressions

typescript
// ❌ AVOID: Impossible to read
const userStatus = user.isAdmin
  ? user.hasPermission
    ? user.isActive
      ? 'Active Admin'
      : 'Inactive Admin'
    : 'Restricted Admin'
  : user.isPremium
    ? 'Premium User'
    : 'Regular User';

// ✅ BETTER: Use helper function
function getUserStatus(user: any): string {
  if (user.isAdmin) {
    if (!user.hasPermission) return 'Restricted Admin';
    return user.isActive ? 'Active Admin' : 'Inactive Admin';
  }
  return user.isPremium ? 'Premium User' : 'Regular User';
}

# Pitfall 3: Missing Dependencies in Conditional Logic

typescript
// ❌ WRONG: condition relies on prop but might not update
interface AlertProps {
  message: string;
  shouldShow?: boolean;
}

function Alert({ message, shouldShow = true }: AlertProps) {
  const [isVisible, setIsVisible] = useState(true);

  return isVisible && <div>{message}</div>;
  // If shouldShow prop changes, component won't update!
}

// ✅ CORRECT: Sync state with prop
import { useEffect } from 'react';

function AlertFixed({ message, shouldShow = true }: AlertProps) {
  const [isVisible, setIsVisible] = useState(shouldShow);

  useEffect(() => {
    setIsVisible(shouldShow);
  }, [shouldShow]);

  return isVisible && <div>{message}</div>;
}

// ✅ EVEN BETTER: Just use the prop directly
function AlertBetter({ message, shouldShow = true }: AlertProps) {
  return shouldShow && <div>{message}</div>;
}

# Pitfall 4: Conditional Tag Names Without Proper Wrapping

typescript
// ❌ WRONG: Can't conditionally pick between block elements
interface HeroProps {
  isLink: boolean;
  href?: string;
  children: ReactNode;
}

function HeroWrong({ isLink, href, children }: HeroProps) {
  // You can't return different tag types based on condition
  const tag = isLink ? 'a' : 'div';
  return <{tag} href={href}>{children}</{tag}>; // Syntax error!
}

// ✅ SOLUTION 1: Use early returns
function HeroFixed1({ isLink, href, children }: HeroProps) {
  if (isLink) {
    return <a href={href}>{children}</a>;
  }
  return <div>{children}</div>;
}

// ✅ SOLUTION 2: Use React.createElement
import { ReactNode, createElement } from 'react';

function HeroFixed2({ isLink, href, children }: HeroProps) {
  const Tag = isLink ? 'a' : 'div';
  return createElement(Tag, { href }, children);
}

# Decision Framework {#decision-framework}

Use this flowchart to choose the right conditional rendering pattern:

typescript
Does component return nothing in one branch?
├─ YES: Use Early Return pattern
└─ NO: Continue

Is it a simple true/false condition?
├─ YES: 
│  └─ Is "else" branch needed?
│     ├─ YES: Use Ternary Operator
│     └─ NO: Use Logical AND (&&)
└─ NO: Continue

Are there 3+ discrete states?
├─ YES: Use Enum Objects
└─ NO: Continue

Is conditional content multi-line?
├─ YES: Use Component Variables
└─ NO: Use Ternary or Logical AND

# Practical Scenarios {#scenarios}

# Real-World Example: Data Table with Multiple States

typescript
import { useState, ReactNode } from 'react';

interface Row {
  id: string;
  name: string;
  status: 'active' | 'inactive';
}

interface DataTableProps {
  rows: Row[] | null;
  isLoading: boolean;
  error: Error | null;
  onRetry: () => void;
}

export function DataTable({
  rows,
  isLoading,
  error,
  onRetry,
}: DataTableProps) {
  const [sortBy, setSortBy] = useState<'name' | 'status'>('name');

  // ✅ Early return for loading state
  if (isLoading) {
    return (
      <div className="table-loading" role="status" aria-busy="true">
        <div className="spinner" />
        <p>Loading data...</p>
      </div>
    );
  }

  // ✅ Early return for error state
  if (error) {
    return (
      <div className="table-error" role="alert">
        <p>Error loading table: {error.message}</p>
        <button onClick={onRetry}>Try Again</button>
      </div>
    );
  }

  // ✅ Early return for empty state
  if (!rows || rows.length === 0) {
    return <div className="table-empty">No data available</div>;
  }

  // ✅ Sort data
  const sortedRows = [...rows].sort((a, b) => {
    if (sortBy === 'name') {
      return a.name.localeCompare(b.name);
    }
    return a.status.localeCompare(b.status);
  });

  // ✅ Happy path — render table
  return (
    <div className="table-container">
      <table>
        <thead>
          <tr>
            <th>
              <button onClick={() => setSortBy('name')}>
                Name {sortBy === 'name' && '↓'}
              </button>
            </th>
            <th>
              <button onClick={() => setSortBy('status')}>
                Status {sortBy === 'status' && '↓'}
              </button>
            </th>
          </tr>
        </thead>
        <tbody>
          {sortedRows.map((row) => (
            <tr key={row.id}>
              <td>{row.name}</td>
              <td>
                <span className={`status status-${row.status}`}>
                  {row.status === 'active' ? '✓ Active' : '○ Inactive'}
                </span>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

# FAQ {#faq}

Q: Should I use && or ternary operator?

A: Use && only when you need to conditionally show/hide a single element. Use ternary when you have two distinct branches (render X or render Y). Use early returns for checking guard conditions at the start of a component.

Q: How do I handle multiple conditions?

A: Combine your conditions before rendering: {condition1 && condition2 && <Component />} or use a helper function: {shouldRender(data) && <Component />}. For complex logic, consider extracting to a separate function for clarity.

Q: Is returning null the same as returning nothing?

A: Yes, returning null from a component renders nothing — no DOM node is created. Both return null and rendering nothing are equivalent.

Q: Can I use inline functions in conditionals?

A: Yes, but it's not recommended: {condition && (() => <Component />)()}. Instead, extract to a separate component or use a helper function. This improves readability and performance.

Q: How do I conditionally apply CSS classes?

A: Use template literals or array methods:

typescript
// Template literal
className={`button ${isActive ? 'button-active' : 'button-inactive'}`}

// Conditional object
className={clsx('button', { 'button-active': isActive })}

Q: What's the performance impact of different patterns?

A: All patterns have negligible performance differences. Focus on readability and maintainability. Early returns and enum objects are slightly more optimized because they prevent unnecessary computations, but the difference is imperceptible in real applications.


Ready to write cleaner React code? Apply these patterns to your next project and notice how much easier your code becomes to read and maintain.

Next Steps: Explore component composition patterns and error boundaries for more advanced conditional rendering scenarios.

Sponsored Content

Google AdSense Placeholder

CONTENT SLOT

Sponsored

Google AdSense Placeholder

FOOTER SLOT