| |

React 21 ⚛️ Conditional Rendering with Ternary Operators

A React component that renders the same output every time is a static component. Real components respond to state, props, and user input. Some of that response is structural: show this when logged in, show that when logged out. React does not have a v-if directive or an *ngIf attribute. It has JavaScript. Conditional rendering in React is not a framework feature; it is the natural consequence of JSX being an expression language. Among the tools React gives you for this, the ternary operator is the most expressive for two-way choices.

This chapter covers conditional rendering with the ternary operator in React. You will learn how JSX embeds expressions, why ternary operators fit naturally into JSX, and how they compare to the logical AND operator for conditional rendering. You will see how ternaries compose with components, lists, and props. You will also learn the patterns that keep complex conditionals readable and the pitfalls that produce the dreaded false rendering as text or the classic “0 appears on screen” bug.

By the end, you will understand not just how to write a ternary in JSX but when it is the right tool—and when &&, early returns, or a lookup object is clearer.

Key point: JSX is JavaScript, not a template language. Anything that is a valid JavaScript expression can appear between curly braces in JSX. The ternary operator is an expression, so it can be used inline in JSX. An if statement is not an expression, so it cannot.


Why ternary operators exist in React

The expression problem. JSX is not HTML. It is syntactic sugar for React.createElement calls, and it compiles to JavaScript expressions. When you write <div>{something}</div>, the something must be an expression—a value that evaluates to something. An if statement does not produce a value; it controls flow. A ternary operator does produce a value. This is why ternaries are the natural inline conditional in JSX and why if statements are not allowed there.

The two-way choice problem. The most common conditional rendering need is a binary choice: show one thing or another. Logged in or logged out. Loading or loaded. Error or success. The ternary operator expresses this directly: condition ? <A /> : <B />. It reads as a question with two answers, which matches the structure of the problem.

The && limitation. The logical AND operator is a compact way to render something only when a condition is true: condition && <Component />. But it has a flaw. If condition is 0, the expression evaluates to 0, and React renders the number 0 on screen. This is the classic “0 appears in the UI” bug. The ternary avoids this because both branches are explicit. When you need an either/or choice, the ternary is safer and clearer.

The readability problem. Ternaries can be nested. Nested ternaries are hard to read. This chapter treats readability as a first-class concern: ternaries are excellent for two-way choices and poor for multi-way choices. When the logic branches more than two ways, other patterns—early returns, lookup objects, or separate components—are clearer.

The React philosophy. React’s documentation is explicit: conditional rendering in React works the same way conditions work in JavaScript. There is no special syntax, no directive, no template language. You use if, &&, ? :, and whatever else JavaScript offers. This is a deliberate design choice. It means React developers need to know JavaScript well, but it also means there is nothing new to learn. The ternary is not a React feature; it is a JavaScript feature used in a React context.


a. Ternary syntax in JSX

The ternary operator takes three operands: a condition, a value if true, and a value if false.

condition ? valueIfTrue : valueIfFalse

In JSX, this appears inside curly braces:

function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in.</h1>}
    </div>
  );
}

The entire ternary is an expression that evaluates to either the first JSX element or the second. React renders whichever one the expression produces.

The condition can be any expression that evaluates to a truthy or falsy value. This includes comparisons, property accesses, function calls, and combinations with && and ||.

{count > 0 ? <p>You have {count} items.</p> : <p>Your cart is empty.</p>}
{user.role === 'admin' ? <AdminPanel /> : <UserPanel />}

A common mistake is forgetting that the ternary is an expression and trying to use an if statement in the same place:

// This is a syntax error
{ if (isLoggedIn) { <h1>Welcome</h1> } else { <h1>Log in</h1> } }

The fix is to use the ternary or to move the logic outside the JSX.


b. Ternary with components and fragments

Ternaries work with any JSX value, including component elements, fragments, and null.

function UserStatus({ user }) {
  return (
    <div>
      {user ? <UserProfile user={user} /> : <LoginPrompt />}
    </div>
  );
}

Returning null from one branch renders nothing:

{showBanner ? <Banner /> : null}

This is equivalent to showBanner && <Banner /> but avoids the 0 rendering bug when showBanner might be a number.

Fragments allow multiple elements in one branch:

{isLoading ? (
  <>
    <Spinner />
    <p>Loading...</p>
  </>
) : (
  <Content data={data} />
)}

The parentheses around the JSX in each branch are not required but improve readability, especially for multi-line JSX.

Ternaries also work in props:

<Button variant={isPrimary ? 'primary' : 'secondary'}>
  {isLoading ? 'Saving...' : 'Save'}
</Button>

And in className:

<div className={isActive ? 'tab active' : 'tab'}>

Anywhere a JavaScript expression is valid, a ternary is valid.


c. Readability and alternatives

Ternaries are excellent for two-way choices. They become hard to read when nested.

// Hard to read
{status === 'loading' ? (
  <Spinner />
) : status === 'error' ? (
  <Error />
) : status === 'empty' ? (
  <Empty />
) : (
  <Content />
)}

This works, but it requires careful reading to understand the branches. The nesting is not visually obvious, and adding another branch makes it worse.

Several alternatives improve readability.

Early returns. Extract the conditional into a function or component that uses if statements:

function StatusDisplay({ status }) {
  if (status === 'loading') return <Spinner />;
  if (status === 'error') return <Error />;
  if (status === 'empty') return <Empty />;
  return <Content />;
}

This is often the clearest form for multi-way branches. Each condition is on its own line, and the fallthrough is explicit.

Lookup objects. Map conditions to components:

const statusComponents = {
  loading: <Spinner />,
  error: <Error />,
  empty: <Empty />,
  success: <Content />
};

return <div>{statusComponents[status] ?? <Content />}</div>;

This is compact and easy to extend. It works best when the branches are simple and the mapping is clear.

Logical AND for single-branch cases. When you only need to render something or nothing, && is more compact:

{isLoading && <Spinner />}

But remember the 0 bug: if isLoading could be 0, use a ternary with null or convert to boolean.

The rule of thumb. Use a ternary for two-way choices. Use early returns or a lookup object for three or more branches. Use && when one branch is “render nothing” and the condition is guaranteed boolean.


Complete Example Session

// ============================================
// PART 1: BASIC TERNARY
// ============================================
function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please log in.</h1>}
    </div>
  );
}
// ============================================
// PART 2: TERNARY WITH COMPONENTS
// ============================================
function UserStatus({ user }) {
  return (
    <div>
      {user ? <UserProfile user={user} /> : <LoginPrompt />}
    </div>
  );
}
// ============================================
// PART 3: TERNARY WITH NULL
// ============================================
function Banner({ show }) {
  return (
    <div>
      {show ? <div className="banner">Welcome!</div> : null}
    </div>
  );
}
// ============================================
// PART 4: TERNARY WITH FRAGMENTS
// ============================================
function LoadingState({ isLoading, data }) {
  return (
    <div>
      {isLoading ? (
        <>
          <Spinner />
          <p>Loading...</p>
        </>
      ) : (
        <Content data={data} />
      )}
    </div>
  );
}
// ============================================
// PART 5: TERNARY IN PROPS
// ============================================
function SaveButton({ isLoading, isPrimary }) {
  return (
    <button
      className={isPrimary ? 'btn-primary' : 'btn-secondary'}
      disabled={isLoading}
    >
      {isLoading ? 'Saving...' : 'Save'}
    </button>
  );
}
// ============================================
// PART 6: THE 0 BUG WITH &&
// ============================================
function ItemCount({ count }) {
  return (
    <div>
      {count && <p>You have {count} items.</p>}
      {/* If count is 0, this renders "0" on screen */}
    </div>
  );
}
// ============================================
// PART 7: TERNARY FIXES THE 0 BUG
// ============================================
function ItemCountFixed({ count }) {
  return (
    <div>
      {count > 0 ? <p>You have {count} items.</p> : null}
      {/* Renders nothing when count is 0 */}
    </div>
  );
}
// ============================================
// PART 8: MULTI-WAY WITH EARLY RETURNS
// ============================================
function StatusDisplay({ status }) {
  if (status === 'loading') return <Spinner />;
  if (status === 'error') return <Error />;
  if (status === 'empty') return <Empty />;
  return <Content />;
}
// ============================================
// PART 9: MULTI-WAY WITH LOOKUP OBJECT
// ============================================
function StatusDisplayLookup({ status }) {
  const components = {
    loading: <Spinner />,
    error: <Error />,
    empty: <Empty />,
    success: <Content />
  };
  return <div>{components[status] ?? <Content />}</div>;
}
// ============================================
// PART 10: NESTED TERNARY (AVOID)
// ============================================
function StatusDisplayNested({ status }) {
  return (
    <div>
      {status === 'loading' ? (
        <Spinner />
      ) : status === 'error' ? (
        <Error />
      ) : status === 'empty' ? (
        <Empty />
      ) : (
        <Content />
      )}
    </div>
  );
}
// Works, but early returns are clearer.

The ten parts covered the essential patterns: basic ternary, ternary with components, ternary with null, ternary with fragments, ternary in props, the 0 bug with &&, the ternary fix, multi-way with early returns, multi-way with lookup objects, and the nested ternary that should usually be refactored.


Quick Reference

Conditional Rendering Patterns

PatternSyntaxUse Case
Ternarycond ? <A /> : <B />Two-way choice
Logical ANDcond && <A />Render or nothing (boolean cond)
Logical ORcond || <A />Fallback rendering
Early returnif (cond) return <A />Multi-way branches
Lookup objectmap[cond]Many branches, simple values
Nullcond ? <A /> : nullSafe “render nothing”

Ternary vs &&

AspectTernary&&
Two-way choiceYesNo (only true branch)
0 bugNoYes
Explicit false branchYesNo
ReadabilityClearCompact
Best forEither/orShow/hide

Multi-Way Branching

BranchesRecommended Pattern
2Ternary
3–4Early returns
5+Lookup object or separate component
DynamicComponent map

Common JSX Expressions

ExpressionValid in JSX
{condition ? <A /> : <B />}Yes
{condition && <A />}Yes
{if (x) { ... }}No (statement)
{items.map(...)}Yes
{value ?? fallback}Yes
`{value

Best Practices

✅ Do This:

// Use ternary for two-way choices
{isLoggedIn ? <Dashboard /> : <Login />}                    // ✅

// Use null for "render nothing"
{showBanner ? <Banner /> : null}                            // ✅

// Use fragments for multiple elements per branch
{isLoading ? (<><Spinner /><Text /></>) : <Content />}      // ✅

// Use early returns for multi-way
if (status === 'loading') return <Spinner />;               // ✅

// Use lookup objects for many branches
const map = { a: <A />, b: <B /> };                         // ✅

// Wrap multi-line JSX in parentheses
{cond ? (
  <Component />
) : (
  <Other />
)}                                                          // ✅

❌ Don’t Do This:

// Don't use if statements inside JSX
{ if (x) { <A /> } }                                        // ❌

// Don't nest ternaries deeply
{a ? <A /> : b ? <B /> : c ? <C /> : <D />}                 // ❌

// Don't use && with numbers
{count && <Text />}                                         // ❌ (renders 0)

// Don't return undefined
{cond ? <A /> : undefined}                                  // ⚠️ (use null)

// Don't forget the false branch
{cond ? <A />}                                              // ❌ (syntax error)

Common Pitfalls

PitfallWhy It HappensFix
0 appears on screen&& with falsy numberUse count > 0 && or ternary
Syntax error with ifStatement used in JSXUse ternary or early return
Hard-to-read nested ternariesToo many branchesRefactor to early returns
undefined returnedTernary false branch missing valueReturn null instead
Empty string renderedFalsy string in &&Use ternary with null
Component re-mountsInline component in ternaryDefine component outside render

Real-World Examples

1. Login State

{user ? <Dashboard user={user} /> : <LoginForm />}

2. Loading Spinner

{isLoading ? <Spinner /> : <DataTable rows={rows} />}

3. Error Message

{error ? <ErrorBanner message={error} /> : null}

4. Empty State

{items.length > 0 ? <ItemList items={items} /> : <EmptyState />}

5. Admin vs User Panel

{user.role === 'admin' ? <AdminPanel /> : <UserPanel />}

6. Button Label

<button>{isSaving ? 'Saving...' : 'Save'}</button>

7. Conditional Class

<div className={isActive ? 'tab active' : 'tab'} />

8. Disabled Attribute

<button disabled={isSubmitting ? true : false}>

9. Theme Toggle

{theme === 'dark' ? <MoonIcon /> : <SunIcon />}

10. Nested Route Content

{isAuthorized ? (
  <Outlet />
) : (
  <Navigate to="/login" />
)}

Visual

JSX Expression Evaluation

┌─────────────────────────────────────────────────────────────┐
│  HOW JSX HANDLES TERNARIES                                  │
│                                                             │
│  {isLoggedIn ? <Dashboard /> : <Login />}                   │
│                                                             │
│  Step 1: Evaluate condition                                 │
│    isLoggedIn = true                                        │
│                                                             │
│  Step 2: Select branch                                      │
│    true → <Dashboard />                                     │
│                                                             │
│  Step 3: React renders the result                           │
│                                                             │
│  The ternary is an expression. It produces a value.         │
│  React renders whatever value the expression produces.      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

The 0 Bug

┌─────────────────────────────────────────────────────────────┐
│  WHY count && <Text /> RENDERS 0                            │
│                                                             │
│  count = 0                                                  │
│                                                             │
│  0 && <Text />                                              │
│                                                             │
│  JavaScript short-circuit evaluation:                       │
│  0 is falsy → expression returns 0 (not false)              │
│                                                             │
│  React renders the value: 0                                 │
│                                                             │
│  Result: "0" appears on screen                              │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  FIX WITH TERNARY                                           │
│                                                             │
│  count > 0 ? <Text /> : null                                │
│                                                             │
│  count = 0 → condition false → null                         │
│  React renders nothing for null                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Choosing a Pattern

┌─────────────────────────────────────────────────────────────┐
│  CONDITIONAL RENDERING DECISION TREE                        │
│                                                             │
│  How many branches?                                         │
│    │                                                        │
│    ├── 2 (either/or) ──▶ Ternary                            │
│    │                                                        │
│    ├── 1 (show or nothing)                                  │
│    │     │                                                  │
│    │     ├── Condition is boolean ──▶ &&                    │
│    │     │                                                  │
│    │     └── Condition may be number ──▶ Ternary with null  │
│    │                                                        │
│    └── 3+                                                   │
│          │                                                  │
│          ├── Static branches ──▶ Lookup object              │
│          │                                                  │
│          └── Dynamic logic ──▶ Early returns                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Ternary vs Early Return

┌─────────────────────────────────────────────────────────────┐
│  TERNARY (2 BRANCHES)                                       │
│                                                             │
│  return (                                                   │
│    <div>                                                    │
│      {user ? <Profile /> : <Login />}                       │
│    </div>                                                   │
│  );                                                         │
│                                                             │
│  Clean, inline, easy to read.                               │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  EARLY RETURN (MULTI-BRANCH)                                │
│                                                             │
│  if (status === 'loading') return <Spinner />;              │
│  if (status === 'error') return <Error />;                  │
│  if (status === 'empty') return <Empty />;                  │
│  return <Content />;                                        │
│                                                             │
│  Each branch on its own line.                               │
│  Fallthrough is explicit.                                   │
│  Easy to add or remove branches.                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
Operatorcondition ? valueIfTrue : valueIfFalse
In JSXWrapped in curly braces: {cond ? <A /> : <B />}
Best forTwo-way choices
Avoid forThree or more branches
null branchRenders nothing
Fragment branch<>...</> for multiple elements
0 bug&& with number renders 0
Fix for 0 bugTernary with null or count > 0
Multi-branch alternativeEarly returns
Many-branch alternativeLookup object

Key takeaways:

  • JSX is an expression language. Only expressions can appear between curly braces. The ternary is an expression. An if statement is not. This is why ternaries work inline in JSX and if statements do not.
  • The ternary is ideal for two-way choices. condition ? <A /> : <B /> expresses an either/or decision directly. It is the most readable form when there are exactly two branches.
  • The && operator has a 0 bug. When the left operand is 0, the expression evaluates to 0, and React renders it. The ternary avoids this because both branches are explicit.
  • null is the safe “render nothing” value. Returning null from a ternary branch renders nothing and avoids the 0 bug. Avoid returning undefined, which can behave inconsistently.
  • Fragments allow multiple elements per branch. When a branch needs more than one element, wrap them in <>...</>. This is cleaner than wrapping in an unnecessary <div>.
  • Nested ternaries are hard to read. A ternary inside a ternary works, but it strains comprehension. When you have three or more branches, early returns or a lookup object is clearer.
  • Early returns are the clearest multi-branch pattern. Extracting the conditional into a function or component with if statements on separate lines makes each branch explicit and easy to modify.
  • Lookup objects scale to many branches. When conditions map to components or values, an object literal is compact and extensible. Use ?? for a default fallback.

Remember: Conditional rendering in React is not a framework feature. It is JavaScript. The ternary operator is one tool among several—&&, early returns, lookup objects, and null all have their place. The ternary is the right choice when the logic is genuinely a two-way choice and both branches are meaningful. When the logic branches more ways, or when the branches are complex, step back and choose a pattern that keeps the code readable. The goal is not to use the ternary everywhere. The goal is to render the right thing at the right time in a way that the next developer can understand.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!