Episode 2 — React Frontend Architecture NextJS / 2.2 — React Components and Props

2.2.c — Dynamic Rendering

In one sentence: Dynamic rendering is the practice of changing what a component displays based on the data it receives through props, making UIs responsive to data without hardcoding content.

Navigation: ← 2.2.b — Understanding Props · Next → 2.2.d — Rendering Lists


Table of Contents

  1. What Is Dynamic Rendering?
  2. Dynamic Text and Numbers
  3. Dynamic Images and Media
  4. Dynamic Styles
  5. Dynamic CSS Classes
  6. Conditional Rendering Basics
  7. Rendering Based on Data Shape
  8. Component Variants with Props
  9. Status Indicators and Badges
  10. Role-Based and Permission-Based UI
  11. Dynamic Component Selection
  12. Template Patterns for Real UIs
  13. Performance Considerations
  14. Common Mistakes
  15. Key Takeaways
  16. Explain-It Challenge

1. What Is Dynamic Rendering?

Static rendering means every user sees the exact same thing. Dynamic rendering means the UI adapts based on data.

┌──────────────────────────────────────────────────────────┐
│         STATIC vs DYNAMIC RENDERING                       │
│                                                          │
│  STATIC (hardcoded):                                     │
│  ┌───────────────────────┐                               │
│  │  Hello, World!         │  ← Same for everyone         │
│  │  You have 0 messages   │  ← Never changes             │
│  │  [ Default Avatar ]    │  ← Fixed image               │
│  └───────────────────────┘                               │
│                                                          │
│  DYNAMIC (data-driven):                                  │
│  ┌───────────────────────┐                               │
│  │  Hello, Alice!         │  ← From props.name           │
│  │  You have 5 messages   │  ← From props.count          │
│  │  [ Alice's Avatar  ]   │  ← From props.avatar         │
│  │  🟢 Online             │  ← From props.status         │
│  │  [Admin Badge]         │  ← From props.role           │
│  └───────────────────────┘                               │
│                                                          │
│  Same component, completely different output.             │
└──────────────────────────────────────────────────────────┘

Everything in React that uses {} inside JSX is dynamic rendering:

// Every {} is a dynamic insertion point
function WelcomeCard({ name, role, messageCount, lastLogin }) {
  return (
    <div className="welcome-card">
      <h1>Welcome back, {name}!</h1>
      <p>Role: {role}</p>
      <p>You have {messageCount} unread messages</p>
      <p>Last login: {lastLogin}</p>
    </div>
  );
}

2. Dynamic Text and Numbers

Basic Text Interpolation

function UserGreeting({ firstName, lastName, title }) {
  return (
    <div>
      {/* Simple interpolation */}
      <h1>Hello, {firstName}!</h1>
      
      {/* Combining multiple props */}
      <h2>{title} {firstName} {lastName}</h2>
      
      {/* Template literal for complex strings */}
      <p>{`Welcome to your dashboard, ${firstName} ${lastName}`}</p>
      
      {/* Expressions */}
      <p>Initials: {firstName[0]}{lastName[0]}</p>
      
      {/* Method calls */}
      <p>Username: {firstName.toLowerCase()}.{lastName.toLowerCase()}</p>
    </div>
  );
}

<UserGreeting firstName="Alice" lastName="Johnson" title="Dr." />
// Renders:
// Hello, Alice!
// Dr. Alice Johnson
// Welcome to your dashboard, Alice Johnson
// Initials: AJ
// Username: alice.johnson

Number Formatting

function PriceDisplay({ amount, currency = "USD", locale = "en-US" }) {
  const formatted = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency,
  }).format(amount);

  return <span className="price">{formatted}</span>;
}

function StatsCard({ label, value, unit, decimals = 0 }) {
  // Format large numbers with commas
  const formattedValue = typeof value === "number"
    ? value.toLocaleString("en-US", { 
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals 
      })
    : value;

  return (
    <div className="stat-card">
      <p className="stat-label">{label}</p>
      <p className="stat-value">
        {formattedValue}
        {unit && <span className="stat-unit"> {unit}</span>}
      </p>
    </div>
  );
}

// Usage
<PriceDisplay amount={1299.99} currency="USD" />   // $1,299.99
<PriceDisplay amount={1299.99} currency="EUR" locale="de-DE" />  // 1.299,99 €
<StatsCard label="Revenue" value={1500000} unit="USD" />  // 1,500,000 USD
<StatsCard label="Conversion" value={3.456} unit="%" decimals={2} />  // 3.46 %

Date Formatting

function DateDisplay({ date, format = "medium" }) {
  const d = new Date(date);
  
  const formats = {
    short: { month: "numeric", day: "numeric", year: "2-digit" },
    medium: { month: "short", day: "numeric", year: "numeric" },
    long: { month: "long", day: "numeric", year: "numeric", weekday: "long" },
    relative: null, // handled separately
  };

  if (format === "relative") {
    return <time dateTime={d.toISOString()}>{getRelativeTime(d)}</time>;
  }

  const formatted = d.toLocaleDateString("en-US", formats[format]);
  return <time dateTime={d.toISOString()}>{formatted}</time>;
}

function getRelativeTime(date) {
  const now = new Date();
  const diffMs = now - date;
  const diffMins = Math.floor(diffMs / 60000);
  const diffHours = Math.floor(diffMs / 3600000);
  const diffDays = Math.floor(diffMs / 86400000);

  if (diffMins < 1) return "Just now";
  if (diffMins < 60) return `${diffMins}m ago`;
  if (diffHours < 24) return `${diffHours}h ago`;
  if (diffDays < 7) return `${diffDays}d ago`;
  return date.toLocaleDateString();
}

// Usage
<DateDisplay date="2026-04-11" format="short" />     // 4/11/26
<DateDisplay date="2026-04-11" format="medium" />     // Apr 11, 2026
<DateDisplay date="2026-04-11" format="long" />       // Friday, April 11, 2026
<DateDisplay date={Date.now() - 300000} format="relative" />  // 5m ago

3. Dynamic Images and Media

function Avatar({ 
  src, 
  name, 
  size = 48, 
  status,
  fallback = "/images/default-avatar.png" 
}) {
  const [imgError, setImgError] = useState(false);

  const statusColors = {
    online: "#4caf50",
    offline: "#9e9e9e",
    busy: "#f44336",
    away: "#ff9800",
  };

  return (
    <div style={{ position: "relative", width: size, height: size }}>
      <img
        src={imgError ? fallback : src}
        alt={`${name}'s avatar`}
        width={size}
        height={size}
        style={{ borderRadius: "50%", objectFit: "cover" }}
        onError={() => setImgError(true)}
      />
      {status && (
        <span
          style={{
            position: "absolute",
            bottom: 0,
            right: 0,
            width: size * 0.25,
            height: size * 0.25,
            borderRadius: "50%",
            backgroundColor: statusColors[status] || "#9e9e9e",
            border: "2px solid white",
          }}
          title={status}
        />
      )}
    </div>
  );
}

// Usage
<Avatar src="/alice.jpg" name="Alice" size={64} status="online" />
<Avatar src="/bob.jpg" name="Bob" size={32} status="busy" />
<Avatar src="/broken-link.jpg" name="Unknown" />  // Falls back to default

Dynamic Background Images

function HeroBanner({ 
  title, 
  subtitle, 
  backgroundImage, 
  overlayOpacity = 0.5 
}) {
  return (
    <section
      style={{
        position: "relative",
        backgroundImage: `url(${backgroundImage})`,
        backgroundSize: "cover",
        backgroundPosition: "center",
        padding: "120px 40px",
        color: "white",
      }}
    >
      <div
        style={{
          position: "absolute",
          inset: 0,
          backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})`,
        }}
      />
      <div style={{ position: "relative", zIndex: 1 }}>
        <h1 style={{ fontSize: "3rem", marginBottom: "1rem" }}>{title}</h1>
        <p style={{ fontSize: "1.25rem" }}>{subtitle}</p>
      </div>
    </section>
  );
}

<HeroBanner 
  title="Summer Collection" 
  subtitle="50% off all items"
  backgroundImage="/images/summer-hero.jpg"
  overlayOpacity={0.6}
/>

4. Dynamic Styles

Inline Styles from Props

function Box({ 
  width = "auto", 
  height = "auto", 
  padding = "16px",
  bgColor = "#ffffff", 
  borderRadius = "8px",
  shadow = false,
  children 
}) {
  const style = {
    width,
    height,
    padding,
    backgroundColor: bgColor,
    borderRadius,
    boxShadow: shadow ? "0 4px 12px rgba(0, 0, 0, 0.15)" : "none",
    transition: "box-shadow 0.2s ease",
  };

  return <div style={style}>{children}</div>;
}

<Box bgColor="#f0f4f8" padding="24px" shadow>
  <h2>Elevated Card</h2>
</Box>

Dynamic Style Objects Based on State/Props

function ProgressBar({ value, max = 100, color = "#2196f3", height = 8 }) {
  const percentage = Math.min(100, Math.max(0, (value / max) * 100));
  
  // Dynamic color based on progress
  const autoColor = percentage < 33 
    ? "#f44336"   // Red for low
    : percentage < 66 
    ? "#ff9800"   // Orange for medium
    : "#4caf50";  // Green for high

  return (
    <div
      style={{
        width: "100%",
        height,
        backgroundColor: "#e0e0e0",
        borderRadius: height / 2,
        overflow: "hidden",
      }}
      role="progressbar"
      aria-valuenow={value}
      aria-valuemin={0}
      aria-valuemax={max}
    >
      <div
        style={{
          width: `${percentage}%`,
          height: "100%",
          backgroundColor: color === "auto" ? autoColor : color,
          borderRadius: height / 2,
          transition: "width 0.3s ease",
        }}
      />
    </div>
  );
}

<ProgressBar value={25} color="auto" />    // Red bar at 25%
<ProgressBar value={50} color="auto" />    // Orange bar at 50%
<ProgressBar value={90} color="auto" />    // Green bar at 90%
<ProgressBar value={75} color="#9c27b0" /> // Purple bar at 75%

5. Dynamic CSS Classes

String Concatenation

function Button({ variant = "primary", size = "medium", fullWidth = false, disabled = false, children, onClick }) {
  // Build class string dynamically
  let className = "btn";
  className += ` btn-${variant}`;
  className += ` btn-${size}`;
  if (fullWidth) className += " btn-full-width";
  if (disabled) className += " btn-disabled";

  return (
    <button className={className} disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

// Renders: <button class="btn btn-primary btn-medium">Click</button>
<Button variant="primary" size="medium">Click</Button>

// Renders: <button class="btn btn-danger btn-large btn-full-width">Delete</button>
<Button variant="danger" size="large" fullWidth>Delete</Button>

Template Literals

function Card({ elevated = false, bordered = false, rounded = true, padding = "medium" }) {
  const className = `
    card
    card-padding-${padding}
    ${elevated ? "card-elevated" : ""}
    ${bordered ? "card-bordered" : ""}
    ${rounded ? "card-rounded" : ""}
  `.trim().replace(/\s+/g, " ");  // Clean up whitespace

  return <div className={className}>{children}</div>;
}

The clsx / classnames Library (Industry Standard)

npm install clsx
import clsx from "clsx";

function Button({ variant, size, fullWidth, disabled, active, children, onClick }) {
  const className = clsx(
    "btn",                          // Always included
    `btn-${variant}`,               // Always included (template)
    `btn-${size}`,                  // Always included (template)
    {
      "btn-full-width": fullWidth,  // Included only if fullWidth is truthy
      "btn-disabled": disabled,     // Included only if disabled is truthy
      "btn-active": active,         // Included only if active is truthy
    }
  );

  return (
    <button className={className} disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

Tailwind CSS Dynamic Classes

function Badge({ variant = "default", size = "medium", children }) {
  const variantClasses = {
    default: "bg-gray-100 text-gray-800",
    success: "bg-green-100 text-green-800",
    warning: "bg-yellow-100 text-yellow-800",
    danger:  "bg-red-100 text-red-800",
    info:    "bg-blue-100 text-blue-800",
  };

  const sizeClasses = {
    small:  "text-xs px-2 py-0.5",
    medium: "text-sm px-2.5 py-0.5",
    large:  "text-base px-3 py-1",
  };

  return (
    <span className={`
      inline-flex items-center rounded-full font-medium
      ${variantClasses[variant]}
      ${sizeClasses[size]}
    `}>
      {children}
    </span>
  );
}

6. Conditional Rendering Basics

React has no special if directive like Vue's v-if. You use plain JavaScript.

Pattern 1: Logical AND (&&)

// Shows the element ONLY if condition is true
function Notification({ count }) {
  return (
    <div>
      <h1>Dashboard</h1>
      {count > 0 && (
        <div className="notification-banner">
          You have {count} new notifications
        </div>
      )}
    </div>
  );
}

// ⚠️ GOTCHA: Watch out for falsy numbers!
function ItemCount({ count }) {
  // ❌ BUG: When count is 0, renders the number 0 on screen
  return <div>{count && <span>{count} items</span>}</div>;
  
  // ✅ FIX: Explicitly check for > 0
  return <div>{count > 0 && <span>{count} items</span>}</div>;
}

Pattern 2: Ternary Operator

// Choose between two elements
function LoginButton({ isLoggedIn, onLogin, onLogout }) {
  return (
    <button onClick={isLoggedIn ? onLogout : onLogin}>
      {isLoggedIn ? "Log Out" : "Log In"}
    </button>
  );
}

// Nested ternaries (avoid nesting more than 2 levels!)
function StatusIcon({ status }) {
  return (
    <span>
      {status === "success" ? "✅" 
        : status === "error" ? "❌" 
        : status === "loading" ? "⏳" 
        : "❓"}
    </span>
  );
}

Pattern 3: Early Return

// Return early for edge cases
function UserProfile({ user, isLoading, error }) {
  if (isLoading) {
    return <LoadingSpinner />;
  }

  if (error) {
    return <ErrorMessage message={error.message} />;
  }

  if (!user) {
    return <EmptyState message="User not found" />;
  }

  // Happy path — render the full component
  return (
    <div className="profile">
      <Avatar src={user.avatar} name={user.name} />
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
}

Pattern 4: Object Lookup (Switch Alternative)

function Icon({ name, size = 24 }) {
  const icons = {
    home:     <HomeIcon size={size} />,
    settings: <SettingsIcon size={size} />,
    profile:  <ProfileIcon size={size} />,
    search:   <SearchIcon size={size} />,
    mail:     <MailIcon size={size} />,
  };

  return icons[name] || <DefaultIcon size={size} />;
}

// Status messages with object lookup
function StatusMessage({ status }) {
  const messages = {
    pending:    { text: "Order received",       color: "#ff9800", icon: "⏳" },
    processing: { text: "Preparing your order", color: "#2196f3", icon: "🔄" },
    shipped:    { text: "On its way!",          color: "#9c27b0", icon: "📦" },
    delivered:  { text: "Delivered",            color: "#4caf50", icon: "✅" },
    cancelled:  { text: "Cancelled",            color: "#f44336", icon: "❌" },
  };

  const info = messages[status] || { text: "Unknown", color: "#9e9e9e", icon: "❓" };

  return (
    <div style={{ color: info.color, display: "flex", alignItems: "center", gap: "8px" }}>
      <span>{info.icon}</span>
      <span>{info.text}</span>
    </div>
  );
}

7. Rendering Based on Data Shape

Real-world data isn't always clean. Dynamic rendering must handle variations.

function ContactCard({ contact }) {
  return (
    <div className="contact-card">
      <h3>{contact.name}</h3>
      
      {/* Only show if email exists */}
      {contact.email && (
        <p>
          <strong>Email:</strong>{" "}
          <a href={`mailto:${contact.email}`}>{contact.email}</a>
        </p>
      )}
      
      {/* Only show if phone exists */}
      {contact.phone && (
        <p>
          <strong>Phone:</strong>{" "}
          <a href={`tel:${contact.phone}`}>{contact.phone}</a>
        </p>
      )}
      
      {/* Only show if address is complete */}
      {contact.address?.street && contact.address?.city && (
        <p>
          <strong>Address:</strong>{" "}
          {contact.address.street}, {contact.address.city}
          {contact.address.state && `, ${contact.address.state}`}
          {contact.address.zip && ` ${contact.address.zip}`}
        </p>
      )}
      
      {/* Show social links if any exist */}
      {(contact.twitter || contact.github || contact.linkedin) && (
        <div className="social-links">
          {contact.twitter && <a href={`https://twitter.com/${contact.twitter}`}>Twitter</a>}
          {contact.github && <a href={`https://github.com/${contact.github}`}>GitHub</a>}
          {contact.linkedin && <a href={contact.linkedin}>LinkedIn</a>}
        </div>
      )}
      
      {/* Fallback when no contact methods */}
      {!contact.email && !contact.phone && (
        <p className="no-contact">No contact information available</p>
      )}
    </div>
  );
}

Handling Arrays of Different Lengths

function SkillBar({ skills }) {
  if (!skills || skills.length === 0) {
    return <p className="text-muted">No skills listed</p>;
  }

  // Show first 5, with "+N more" for overflow
  const visible = skills.slice(0, 5);
  const remaining = skills.length - 5;

  return (
    <div className="skill-bar">
      {visible.map(skill => (
        <span key={skill} className="skill-tag">{skill}</span>
      ))}
      {remaining > 0 && (
        <span className="skill-overflow">+{remaining} more</span>
      )}
    </div>
  );
}

8. Component Variants with Props

The variant pattern is one of the most common in production React. A single component renders differently based on a variant prop.

function Alert({ 
  variant = "info",
  title,
  children,
  icon,
  dismissible = false,
  onDismiss 
}) {
  const config = {
    info: {
      bg: "#e3f2fd",
      border: "#2196f3",
      text: "#1565c0",
      defaultIcon: "ℹ️",
    },
    success: {
      bg: "#e8f5e9",
      border: "#4caf50",
      text: "#2e7d32",
      defaultIcon: "✅",
    },
    warning: {
      bg: "#fff3e0",
      border: "#ff9800",
      text: "#e65100",
      defaultIcon: "⚠️",
    },
    error: {
      bg: "#ffebee",
      border: "#f44336",
      text: "#c62828",
      defaultIcon: "❌",
    },
  };

  const c = config[variant] || config.info;

  return (
    <div
      role="alert"
      style={{
        display: "flex",
        alignItems: "flex-start",
        gap: "12px",
        padding: "16px",
        backgroundColor: c.bg,
        borderLeft: `4px solid ${c.border}`,
        borderRadius: "4px",
        color: c.text,
      }}
    >
      <span style={{ fontSize: "1.25rem" }}>{icon || c.defaultIcon}</span>
      <div style={{ flex: 1 }}>
        {title && <strong style={{ display: "block", marginBottom: "4px" }}>{title}</strong>}
        <div>{children}</div>
      </div>
      {dismissible && onDismiss && (
        <button
          onClick={onDismiss}
          style={{
            background: "none",
            border: "none",
            fontSize: "1.25rem",
            cursor: "pointer",
            color: c.text,
            padding: 0,
          }}
          aria-label="Dismiss"
        >
          ×
        </button>
      )}
    </div>
  );
}

// Usage
<Alert variant="success" title="Payment Received">
  Your transaction was processed successfully.
</Alert>

<Alert variant="error" title="Upload Failed" dismissible onDismiss={handleDismiss}>
  The file exceeded the 5MB limit. Please try again.
</Alert>

9. Status Indicators and Badges

Online Status Indicator

function StatusDot({ status, showLabel = false, size = 10 }) {
  const statusMap = {
    online:  { color: "#4caf50", label: "Online" },
    offline: { color: "#9e9e9e", label: "Offline" },
    busy:    { color: "#f44336", label: "Do not disturb" },
    away:    { color: "#ff9800", label: "Away" },
    idle:    { color: "#ffc107", label: "Idle" },
  };

  const info = statusMap[status] || statusMap.offline;

  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: "6px" }}>
      <span
        style={{
          display: "inline-block",
          width: size,
          height: size,
          borderRadius: "50%",
          backgroundColor: info.color,
          // Pulse animation for "online"
          animation: status === "online" ? "pulse 2s infinite" : "none",
        }}
        title={info.label}
      />
      {showLabel && <span style={{ fontSize: "0.875rem" }}>{info.label}</span>}
    </span>
  );
}

Notification Badge

function NotificationBadge({ count, max = 99, children }) {
  if (!count || count <= 0) return <>{children}</>;

  const display = count > max ? `${max}+` : String(count);

  return (
    <div style={{ position: "relative", display: "inline-block" }}>
      {children}
      <span
        style={{
          position: "absolute",
          top: "-8px",
          right: "-8px",
          minWidth: "20px",
          height: "20px",
          padding: "0 6px",
          fontSize: "0.75rem",
          fontWeight: "bold",
          color: "white",
          backgroundColor: "#f44336",
          borderRadius: "10px",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          lineHeight: 1,
        }}
      >
        {display}
      </span>
    </div>
  );
}

// Usage
<NotificationBadge count={5}>
  <MailIcon />
</NotificationBadge>

<NotificationBadge count={150} max={99}>
  <BellIcon />
</NotificationBadge>
// Shows "99+"

Priority Tag

function PriorityTag({ level }) {
  const config = {
    critical: { bg: "#d32f2f", text: "white", label: "Critical" },
    high:     { bg: "#f57c00", text: "white", label: "High" },
    medium:   { bg: "#fbc02d", text: "#333",  label: "Medium" },
    low:      { bg: "#4caf50", text: "white", label: "Low" },
    none:     { bg: "#e0e0e0", text: "#666",  label: "None" },
  };

  const c = config[level] || config.none;

  return (
    <span style={{
      display: "inline-block",
      padding: "2px 10px",
      fontSize: "0.75rem",
      fontWeight: 600,
      borderRadius: "12px",
      backgroundColor: c.bg,
      color: c.text,
      textTransform: "uppercase",
      letterSpacing: "0.5px",
    }}>
      {c.label}
    </span>
  );
}

10. Role-Based and Permission-Based UI

function Dashboard({ user }) {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Everyone sees this */}
      <UserProfile user={user} />
      
      {/* Only admins and moderators */}
      {(user.role === "admin" || user.role === "moderator") && (
        <ModeratorPanel />
      )}
      
      {/* Only admins */}
      {user.role === "admin" && (
        <AdminSettings />
      )}
      
      {/* Feature flags */}
      {user.features?.includes("beta_dashboard") && (
        <BetaDashboard />
      )}
    </div>
  );
}

// Reusable permission gate component
function RequireRole({ user, roles, children, fallback = null }) {
  if (!roles.includes(user.role)) {
    return fallback;
  }
  return children;
}

// Usage
<RequireRole user={user} roles={["admin", "moderator"]}>
  <DeleteButton />
</RequireRole>

<RequireRole 
  user={user} 
  roles={["admin"]} 
  fallback={<p>You don't have permission to view this.</p>}
>
  <AdminPanel />
</RequireRole>

Permission-Based with Granular Checks

function ActionBar({ permissions, onEdit, onDelete, onPublish, onArchive }) {
  return (
    <div className="action-bar">
      {permissions.canEdit && (
        <button onClick={onEdit} className="btn-edit">Edit</button>
      )}
      {permissions.canDelete && (
        <button onClick={onDelete} className="btn-delete">Delete</button>
      )}
      {permissions.canPublish && (
        <button onClick={onPublish} className="btn-publish">Publish</button>
      )}
      {permissions.canArchive && (
        <button onClick={onArchive} className="btn-archive">Archive</button>
      )}
      {/* Show message if no actions available */}
      {!permissions.canEdit && !permissions.canDelete && 
       !permissions.canPublish && !permissions.canArchive && (
        <p className="text-muted">No actions available</p>
      )}
    </div>
  );
}

// Usage
<ActionBar 
  permissions={{ canEdit: true, canDelete: false, canPublish: true, canArchive: false }}
  onEdit={handleEdit}
  onPublish={handlePublish}
/>

11. Dynamic Component Selection

Sometimes the component itself changes based on data.

Using an Object Map

import HomeIcon from "./icons/HomeIcon";
import SettingsIcon from "./icons/SettingsIcon";
import UserIcon from "./icons/UserIcon";
import MailIcon from "./icons/MailIcon";

const iconMap = {
  home: HomeIcon,
  settings: SettingsIcon,
  user: UserIcon,
  mail: MailIcon,
};

function DynamicIcon({ name, ...props }) {
  const IconComponent = iconMap[name];
  
  if (!IconComponent) {
    console.warn(`Icon "${name}" not found`);
    return null;
  }
  
  return <IconComponent {...props} />;
}

// Usage
<DynamicIcon name="home" size={24} color="blue" />
<DynamicIcon name="settings" size={32} />

Dynamic Form Fields

const fieldComponents = {
  text:     TextInput,
  email:    EmailInput,
  password: PasswordInput,
  select:   SelectInput,
  textarea: TextAreaInput,
  checkbox: CheckboxInput,
  radio:    RadioGroup,
};

function DynamicForm({ fields, values, onChange }) {
  return (
    <form>
      {fields.map(field => {
        const FieldComponent = fieldComponents[field.type];
        
        if (!FieldComponent) {
          console.warn(`Unknown field type: ${field.type}`);
          return null;
        }
        
        return (
          <FieldComponent
            key={field.name}
            label={field.label}
            name={field.name}
            value={values[field.name]}
            onChange={(value) => onChange(field.name, value)}
            required={field.required}
            options={field.options}
            placeholder={field.placeholder}
          />
        );
      })}
    </form>
  );
}

// Usage — form structure driven by data
const formFields = [
  { type: "text",     name: "firstName", label: "First Name", required: true },
  { type: "text",     name: "lastName",  label: "Last Name",  required: true },
  { type: "email",    name: "email",     label: "Email",      required: true },
  { type: "select",   name: "role",      label: "Role",       options: ["User", "Admin", "Mod"] },
  { type: "textarea", name: "bio",       label: "Bio",        placeholder: "Tell us about yourself..." },
];

<DynamicForm fields={formFields} values={formValues} onChange={handleFieldChange} />

12. Template Patterns for Real UIs

Product Card (E-Commerce)

function ProductCard({ product }) {
  const { name, price, originalPrice, image, rating, reviewCount, inStock, badge } = product;
  
  const discount = originalPrice 
    ? Math.round((1 - price / originalPrice) * 100)
    : 0;

  return (
    <div className="product-card">
      {/* Image with badge */}
      <div style={{ position: "relative" }}>
        <img src={image} alt={name} style={{ width: "100%", aspectRatio: "1", objectFit: "cover" }} />
        {badge && (
          <span style={{
            position: "absolute", top: 8, left: 8,
            padding: "4px 8px", borderRadius: "4px",
            backgroundColor: badge === "New" ? "#2196f3" : "#f44336",
            color: "white", fontSize: "0.75rem", fontWeight: "bold",
          }}>
            {badge}
          </span>
        )}
        {!inStock && (
          <div style={{
            position: "absolute", inset: 0,
            backgroundColor: "rgba(0,0,0,0.5)",
            display: "flex", alignItems: "center", justifyContent: "center",
            color: "white", fontWeight: "bold", fontSize: "1.25rem",
          }}>
            Out of Stock
          </div>
        )}
      </div>

      {/* Details */}
      <div style={{ padding: "12px" }}>
        <h3 style={{ fontSize: "1rem", marginBottom: "8px" }}>{name}</h3>
        
        {/* Rating */}
        <div style={{ display: "flex", alignItems: "center", gap: "4px", marginBottom: "8px" }}>
          {"★".repeat(Math.round(rating))}{"☆".repeat(5 - Math.round(rating))}
          <span style={{ fontSize: "0.875rem", color: "#666" }}>({reviewCount})</span>
        </div>

        {/* Price */}
        <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
          <span style={{ fontSize: "1.25rem", fontWeight: "bold" }}>
            ${price.toFixed(2)}
          </span>
          {originalPrice && (
            <>
              <span style={{ textDecoration: "line-through", color: "#999", fontSize: "0.875rem" }}>
                ${originalPrice.toFixed(2)}
              </span>
              <span style={{ color: "#f44336", fontSize: "0.875rem", fontWeight: "bold" }}>
                -{discount}%
              </span>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

Comment Thread

function Comment({ author, avatar, content, timestamp, likes, replies = [], depth = 0 }) {
  const maxDepth = 3;

  return (
    <div style={{ marginLeft: depth > 0 ? 24 : 0, borderLeft: depth > 0 ? "2px solid #e0e0e0" : "none", paddingLeft: depth > 0 ? 16 : 0 }}>
      <div style={{ display: "flex", gap: "12px", marginBottom: "8px" }}>
        <img src={avatar} alt={author} style={{ width: 32, height: 32, borderRadius: "50%" }} />
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
            <strong>{author}</strong>
            <span style={{ color: "#999", fontSize: "0.875rem" }}>{timestamp}</span>
          </div>
          <p style={{ margin: "4px 0" }}>{content}</p>
          <div style={{ display: "flex", gap: "16px", fontSize: "0.875rem", color: "#666" }}>
            <button style={{ background: "none", border: "none", cursor: "pointer" }}>
              ❤️ {likes}
            </button>
            {depth < maxDepth && (
              <button style={{ background: "none", border: "none", cursor: "pointer" }}>
                Reply
              </button>
            )}
          </div>
        </div>
      </div>

      {/* Nested replies */}
      {replies.length > 0 && depth < maxDepth && (
        <div>
          {replies.map(reply => (
            <Comment key={reply.id} {...reply} depth={depth + 1} />
          ))}
        </div>
      )}
    </div>
  );
}

13. Performance Considerations

Avoid Creating Objects/Arrays Inside JSX

// ❌ BAD — new object every render, causes child to re-render
function Parent() {
  return <Child style={{ color: "red" }} items={[1, 2, 3]} />;
}

// ✅ GOOD — stable references
const childStyle = { color: "red" };
const items = [1, 2, 3];

function Parent() {
  return <Child style={childStyle} items={items} />;
}

// ✅ ALSO GOOD — useMemo for dynamic values
function Parent({ color }) {
  const style = useMemo(() => ({ color }), [color]);
  return <Child style={style} />;
}

Avoid Inline Function Props When Possible

// ❌ New function on every render
function TodoList({ todos, onToggle }) {
  return todos.map(todo => (
    <TodoItem 
      key={todo.id}
      todo={todo}
      onToggle={() => onToggle(todo.id)}  // New function each render
    />
  ));
}

// ✅ Pass ID as prop, handle in child
function TodoList({ todos, onToggle }) {
  return todos.map(todo => (
    <TodoItem 
      key={todo.id}
      todo={todo}
      onToggle={onToggle}  // Stable reference
    />
  ));
}

function TodoItem({ todo, onToggle }) {
  const handleToggle = () => onToggle(todo.id);  // Create in child
  return <li onClick={handleToggle}>{todo.text}</li>;
}

14. Common Mistakes

Mistake 1: Rendering Objects Directly

// ❌ CRASHES — React can't render objects
function UserInfo({ user }) {
  return <p>{user}</p>;  // Error: Objects are not valid as a React child
}

// ✅ Access specific properties
function UserInfo({ user }) {
  return <p>{user.name} ({user.email})</p>;
}

Mistake 2: Forgetting that 0 Renders

// ❌ Shows "0" on screen when count is 0
function Notifications({ count }) {
  return <div>{count && <Badge count={count} />}</div>;
}

// ✅ Explicit boolean check
function Notifications({ count }) {
  return <div>{count > 0 && <Badge count={count} />}</div>;
}

// ✅ Or use ternary
function Notifications({ count }) {
  return <div>{count > 0 ? <Badge count={count} /> : null}</div>;
}

Mistake 3: Complex Ternary Nesting

// ❌ Unreadable nested ternaries
return (
  <div>
    {status === "loading" ? <Spinner /> : status === "error" ? <Error /> : status === "empty" ? <Empty /> : <Data />}
  </div>
);

// ✅ Use early returns or object lookup
function Content({ status, data, error }) {
  if (status === "loading") return <Spinner />;
  if (status === "error") return <Error message={error} />;
  if (status === "empty") return <Empty />;
  return <DataDisplay data={data} />;
}

15. Key Takeaways

  1. Dynamic rendering = UI that changes based on data, not hardcoded content
  2. Use {} in JSX to insert any JavaScript expression — variables, function calls, ternaries
  3. Format data before rendering — numbers, dates, currencies should be human-readable
  4. Dynamic classes — use clsx or template literals for conditional CSS classes
  5. Conditional rendering&&, ternary, early return, object lookup are your four tools
  6. Watch for 0 and "" — these are falsy but renderable; use explicit boolean checks
  7. Component variants — one component with a variant prop beats multiple similar components
  8. Dynamic component selection — use object maps to pick components based on data
  9. Role/permission-based UI — control visibility with boolean checks or gate components
  10. Performance — avoid creating new objects/arrays/functions inside JSX on every render

Explain-It Challenge

  1. The Newspaper: Imagine a newspaper template where the layout stays the same but content changes daily. How does this relate to React's dynamic rendering? What are the "props" of a newspaper? What would a "hardcoded" newspaper look like?

  2. The Bouncer: Explain conditional rendering using the analogy of a bouncer at a club. Different conditions (age, VIP status, dress code) determine who gets in. How does this map to &&, ternary, and early return patterns?

  3. The Custom T-Shirt Shop: A shop has one T-shirt template but can customize color, text, size, and print design. Explain how this maps to a React component with variant props. Why is one flexible component better than 50 hardcoded variations?


Navigation: ← 2.2.b — Understanding Props · Next → 2.2.d — Rendering Lists