9. Conditionals & Lists

Chapter 9: Conditional Rendering & Lists

Dynamic user interfaces constantly adapt: showing loading skeletons while data fetches, displaying error alerts when requests fail, and rendering arrays of products. Master the 5 patterns of conditional rendering and understand how React's reconciliation engine uses keys to render dynamic lists at maximum speed.

1. The Five Patterns of Conditional Rendering

Unlike frameworks that invent custom template syntax (like *ngIf or v-if), React relies entirely on standard JavaScript logic.

Pattern A: Early Return (Cleanest for Guard Clauses)

Ideal when handling loading states or permission errors before reaching the main layout:

function UserProfile({ user, isLoading, error }) {
  if (isLoading) {
    return <div className="spinner">Loading user profile...</div>;
  }

  if (error) {
    return <div className="alert-danger">Failed to load: {error.message}</div>;
  }

  if (!user) {
    return null; // Renders nothing to the DOM
  }

  // Happy-path UI renders cleanly without nested checks
  return (
    <article className="profile">
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </article>
  );
}

Pattern B: The Ternary Operator (`condition ? a : b`)

Used directly inside JSX when you need to toggle between two mutually exclusive views:

function AuthStatus({ isLoggedIn, user, onLogin, onLogout }) {
  return (
    <nav className="auth-bar">
      {isLoggedIn ? (
        <div>
          <span>Welcome, {user.name}!</span>
          <button onClick={onLogout}>Log Out</button>
        </div>
      ) : (
        <button onClick={onLogin}>Log In</button>
      )}
    </nav>
  );
}

Pattern C: Logical AND (`&&`) & The Dreaded '0' Bug

When you want to render an element only when a condition is true, and render nothing when false:

function NotificationBadge({ unreadMessages }) {
  return (
    <div className="inbox">
      <span>Inbox</span>
      {/* ⚠️ GOTCHA: If unreadMessages is 0, this renders: 0 on your screen! */}
      {/* {unreadMessages && <span className="badge">{unreadMessages}</span>} */}

      {/* ✅ CORRECT: Explicit boolean evaluation prevents rendering 0 */}
      {unreadMessages > 0 && <span className="badge">{unreadMessages}</span>}
    </div>
  );
}
⚠️ Why `0 && <JSX />` Renders `0`

JavaScript evaluates the expression left && right by checking the falsiness of left. If left is false, it returns the value of left itself. If left is 0, the expression returns the number 0. React treats strings and numbers as valid text nodes and renders a literal "0" into the DOM! Always use count > 0 && ... or Boolean(count) && ....

2. Rendering Dynamic Lists with `Array.prototype.map()`

To render repeated data collections (such as search results, comments, or product lists), use JavaScript's native map() method to transform raw data arrays into arrays of JSX elements:

const products = [
  { id: "p1", name: "Wireless Headphones", price: 99.99 },
  { id: "p2", name: "Mechanical Keyboard", price: 149.50 },
  { id: "p3", name: "Ergonomic Mouse", price: 49.00 }
];

function ProductCatalog() {
  return (
    <section className="catalog">
      <h2>Featured Products</h2>
      <ul className="product-list">
        {products.map(item => (
          <li key={item.id} className="catalog-item">
            <span className="item-name">{item.name}</span>
            <span className="item-price">${item.price.toFixed(2)}</span>
          </li>
        ))}
      </ul>
    </section>
  );
}

3. Why the `key` Prop Matters for Reconciliation

Keys serve as persistent identities that inform React which items in an array have changed, been added, or been removed. Without keys, if you insert an item at the beginning of an array of 1,000 items, React would destructively re-render and mutate all 1,000 DOM nodes!

🔍 Why You Must Avoid `key={index}`

Using array indexes as keys (e.g., key={index}) works only for purely static, non-reordered lists. If items can be sorted, filtered, or deleted, index-based keys cause critical bugs:

  • If you delete the first item, the item previously at index 1 now has index 0. React believes the first item merely updated its text!
  • If the list items contain internal state (like an <input /> where the user typed text), the user's input will remain stuck in the first row instead of moving with the deleted item!

Rules for Proper Keys:

  • Keys Must Be Unique Among Siblings: Different lists may use the same keys, but items within the same parent array must have unique keys.
  • Keys Must Be Stable: Never generate keys on the fly during render (e.g., key={Math.random()}). A newly generated key forces React to completely destroy and re-create the DOM node on every single render, causing severe performance degradation and loss of input focus!
  • Use Natural Database IDs: Use persistent primary keys from your backend database (e.g., item.id or item.uuid).

Frequently Asked Questions (FAQ)

Why does 'count && <Component />' sometimes render the number '0' on screen in React?

In JavaScript, if the left-hand operand of the && operator evaluates to a falsy value, JavaScript returns that falsy value itself. If 'count' is 0, JavaScript evaluates '0 && <Component />' to 0. Since React renders numbers directly into the DOM, it renders a literal '0'. To prevent this, write 'count > 0 && <Component />' or 'Boolean(count) && <Component />'.

Why is using array index as a key (key={index}) discouraged in React?

Array indexes change whenever items are sorted, inserted at the beginning, or deleted. React relies on keys to preserve the identity and internal state of components across renders. If you use indexes and reorder a list containing stateful inputs, the inputs' state will stay attached to the old index rather than moving with the data item.

How can a component render nothing without breaking React?

In React, a component can simply return null (e.g., if (!isVisible) return null;). React will render nothing to the DOM for that component while continuing to execute its lifecycle and hooks properly.

What is the Early Return pattern in React components?

The early return pattern checks for special states (such as loading spinners, error banners, or unauthorized access) at the very top of a component function and immediately returns the corresponding JSX, keeping the main happy-path JSX at the bottom clean and readable without deeply nested ternary operators.

Explore Related Tools

Hand-picked utilities and calculators related to this tool.

Related Tools