Skip to content
elephantoo

Conditional rendering

Lesson 7 of 30 12 min read

if, ternaries, && and early returns — showing the right UI for each state.


Real interfaces change depending on the situation: show a spinner while loading, an error if something failed, a "log in" button for guests and an avatar for members, an empty-state message when a list has no items. In React, conditional rendering is just JavaScript — if, &&, ? : — applied to JSX.

if statements and early returns#

Because a component is a function, you can return different JSX from different branches:

JSX
function Greeting({ user }) {
  if (!user) {
    return <p>Welcome, guest! Please log in.</p>;
  }
  return <p>Welcome back, {user.name}!</p>;
}

Early returns are the clearest pattern for states that change the whole component:

JSX
function CourseList({ status, courses, error }) {
  if (status === "loading") return <p>Loading courses…</p>;
  if (status === "error") return <p role="alert">Couldn't load courses: {error}</p>;
  if (courses.length === 0) return <p>No courses yet — check back soon!</p>;

  return (
    <ul>
      {courses.map((c) => (
        <li key={c.slug}>{c.title}</li>
      ))}
    </ul>
  );
}

Each case is handled at the top; the "happy path" JSX at the bottom doesn't have to worry about missing data.

Hooks must still run in the same order every render, so put all your useState/useEffect calls above any early return.

Rendering nothing: null#

JSX
function Banner({ message }) {
  if (!message) return null;
  return <div className="banner">{message}</div>;
}

Returning null means "render nothing". The parent can always render <Banner message={msg} /> without checking first.

The ternary operator ? :#

For "this or that" inside JSX, use the conditional operator:

JSX
function LessonItem({ title, done }) {
  return (
    <li className={done ? "lesson done" : "lesson"}>
      {done ? <s>{title}</s> : title} {done ? "✅" : "⏳"}
    </li>
  );
}

Ternaries work anywhere an expression does: in text, attributes and class names.

Logical &&: "this or nothing"#

condition && <JSX /> renders the JSX only when the condition is truthy:

JSX
function Inbox({ unread, isAdmin }) {
  return (
    <header>
      <h1>Inbox</h1>
      {unread > 0 && <span className="badge">{unread} new</span>}
      {isAdmin && <button type="button">Admin settings</button>}
    </header>
  );
}

The 0 trap

React renders numbers, including 0:

JSX
function Cart({ items }) {
  return (
    <div>
      {items.length && <p>You have {items.length} items</p>}
      {/* ❌ when the cart is empty, this renders a lonely "0" */}

      {items.length > 0 && <p>You have {items.length} items</p>}
      {/* ✅ a real boolean */}
    </div>
  );
}

Make the left side of && a boolean: count > 0, Boolean(value), !!value, or list.length !== 0.

Storing JSX in variables#

For more complex logic, compute a piece of JSX before the return:

JSX
function PriceTag({ price, salePrice }) {
  let content;
  if (salePrice != null && salePrice < price) {
    content = (
      <>
        <s>₹{price}</s> <strong>₹{salePrice}</strong> <span className="badge">Sale</span>
      </>
    );
  } else if (price === 0) {
    content = <strong>Free</strong>;
  } else {
    content = <strong>₹{price}</strong>;
  }

  return <p className="price">{content}</p>;
}

This keeps the JSX skeleton tidy and the logic in plain if/else.

Choosing from many options: lookup objects and switch#

JSX
const STATUS_UI = {
  draft: { label: "Draft", icon: "📝" },
  review: { label: "In review", icon: "👀" },
  published: { label: "Published", icon: "✅" },
};

function StatusBadge({ status }) {
  const ui = STATUS_UI[status] ?? { label: "Unknown", icon: "❔" };
  return (
    <span className={`status status-${status}`}>
      {ui.icon} {ui.label}
    </span>
  );
}

function StepContent({ step }) {
  switch (step) {
    case 1:
      return <AccountDetails />;
    case 2:
      return <ChoosePlan />;
    case 3:
      return <Confirm />;
    default:
      return null;
  }
}

Avoid nested ternaries#

JSX
// ❌ hard to read
{status === "loading" ? <Spinner /> : status === "error" ? <ErrorMessage /> : items.length === 0 ? <Empty /> : <List items={items} />}

Move this into early returns, a switch, or a small component.

Showing and hiding: conditional rendering vs CSS#

There are two ways to "hide" something:

JSX
function Details({ open, children }) {
  return (
    <>
      {/* 1. Not rendered at all: removed from the DOM, its state is reset */}
      {open && <div className="panel">{children}</div>}

      {/* 2. Rendered but hidden: stays in the DOM, keeps its state */}
      <div className="panel" hidden={!open}>
        {children}
      </div>
    </>
  );
}
  • Conditional rendering (open && ...) unmounts the element. Any state inside it (like text typed into an input) is lost when it disappears.
  • hidden / CSS keeps it mounted, preserving state and avoiding re-creating expensive content — useful for tabs.

A complete example: a toggle panel#

JSX
import { useState } from "react";

export default function FAQ() {
  const [openId, setOpenId] = useState(null);
  const faqs = [
    { id: "free", q: "Is Elephantoo free?", a: "Yes — every course is free." },
    { id: "cert", q: "Do I need to sign up?", a: "No. Your progress is saved in your browser." },
  ];

  return (
    <div>
      {faqs.map((f) => {
        const isOpen = openId === f.id;
        return (
          <section key={f.id}>
            <h3>
              <button
                type="button"
                aria-expanded={isOpen}
                onClick={() => setOpenId(isOpen ? null : f.id)}
              >
                {isOpen ? "▾" : "▸"} {f.q}
              </button>
            </h3>
            {isOpen && <p>{f.a}</p>}
          </section>
        );
      })}
    </div>
  );
}

Only one answer is open at a time because a single openId state controls them all. aria-expanded tells screen readers whether each question is open.

Common mistakes#

  • {count && <X />} rendering 0.
  • Putting hooks after an early return.
  • Deeply nested ternaries.
  • Forgetting that conditionally-rendered components lose their state when hidden.
  • Using if inside JSX braces — it's a statement; use a ternary or compute before return.

What's next#

Next: rendering lists with map and choosing good keys — plus your first look at effects.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What does {count && <Badge />} render when count is 0?

  2. 2.What should a component return to render nothing?

  3. 3.Which is the clearest way to choose between three or more different UIs, like loading/error/success?

Finished reading?

Mark this lesson complete to track your progress.