Conditional Rendering

React · Lesson 6 of 29 6 min read Updated

Real interfaces change with the data. A "Log in" button turns into the user's name, an order shows "Shipped" or "Delivered", and an empty cart shows a friendly message instead of a list. Showing different JSX for different conditions is called conditional rendering.

React has no special syntax for this. You use ordinary JavaScript: if statements, the ternary operator ? : and the logical && operator. In this lesson you will learn when to use each one, how to avoid the famous "0 on the screen" bug, and how React 19.2's <Activity> component hides UI without losing its state.

if statements and early return

The simplest way is an if statement before the return. A component can return different JSX depending on its props, or return null to show nothing at all.

React JSXTry it yourself
import { createRoot } from "react-dom/client";

function DeliveryStatus({ status, city }) {
  if (status === "cancelled") {
    return <p style={{ color: "crimson" }}>This order was cancelled.</p>;
  }
  if (status === "delivered") {
    return <p style={{ color: "green" }}>Delivered in {city}. Enjoy!</p>;
  }
  if (!status) {
    return null; // nothing to show yet
  }
  return <p>On the way to {city}: {status}</p>;
}

function Orders() {
  return (
    <>
      <DeliveryStatus status="out for delivery" city="Pune" />
      <DeliveryStatus status="delivered" city="Jaipur" />
      <DeliveryStatus status="cancelled" city="Kochi" />
      <DeliveryStatus city="Lucknow" />
    </>
  );
}

createRoot(document.getElementById("root")).render(<Orders />);

The last order has no status, so its component returns null and nothing appears. Returning early keeps each case short and easy to read. Use if when the branches are large or completely different.

The ternary operator inside JSX

You cannot write an if statement inside curly braces, because braces need an expression. The ternary operator condition ? a : b is an expression, so it works anywhere in JSX. Use it when only a small part of the output changes.

The && operator is a shortcut for "show this or nothing". condition && <Thing /> gives <Thing /> when the condition is true, and false (which renders nothing) when it is false.

React JSXTry it yourself
import { createRoot } from "react-dom/client";

function Inbox({ user, unread }) {
  return (
    <div style={{ borderBottom: "1px solid #ddd", padding: 8 }}>
      <p>{user ? `Welcome back, ${user}` : "Welcome, guest"}</p>

      {/* Bug: when unread is 0, React prints "0" */}
      <p>Wrong: {unread && `You have ${unread} new messages`}</p>

      {/* Fixed: the left side is now a real true/false value */}
      <p>Right: {unread > 0 && `You have ${unread} new messages`}</p>

      {!user && <button>Log in</button>}
    </div>
  );
}

createRoot(document.getElementById("root")).render(
  <>
    <Inbox user="Priya" unread={3} />
    <Inbox user="Rohan" unread={0} />
    <Inbox unread={0} />
  </>
);

Look at Rohan's box: the "Wrong" line shows a stray 0. The && operator returns its left side when that side is falsy, and 0 is a number, which React renders. This is the most common conditional-rendering bug. Make the left side a real boolean with a comparison (unread > 0), Boolean(unread) or !!unread.

Storing JSX in a variable

When the logic is too long for a ternary but you still want one return, prepare the varying part in a variable first. JSX is just a value, so this works naturally:

React JSX
function SeatInfo({ seatsLeft }) {
  let message;
  if (seatsLeft === 0) {
    message = <strong>Waiting list only</strong>;
  } else if (seatsLeft < 10) {
    message = <span>Hurry, only {seatsLeft} seats left</span>;
  } else {
    message = <span>Seats available</span>;
  }

  return (
    <div className="seat-info">
      <h3>Rajdhani Express, 3AC</h3>
      {message}
    </div>
  );
}

For many fixed cases, an object that maps each value to its label or colour is often cleaner than a long if chain: const labels = { placed: "Order placed", shipped: "Shipped" }; and then {labels[status]}.

Advanced: hiding UI without losing it

When a condition becomes false, React removes that part of the tree. Any state inside it, such as text typed into an input, is thrown away. Usually that is what you want. Sometimes it is not: switching between tabs should not erase a half-filled form.

React 19.2 added the <Activity> component for this case. With mode="hidden", React hides the children (using CSS display: none), keeps their state, and pauses their effects until they are visible again. Try typing in both tabs of each version below, then switch tabs:

React JSXTry it yourself
import { useState, Activity } from "react";
import { createRoot } from "react-dom/client";

function NoteBox({ label }) {
  return <textarea placeholder={`Type your ${label} notes...`} rows={3} cols={30} />;
}

function Tabs({ keepAlive }) {
  const [tab, setTab] = useState("maths");
  return (
    <section style={{ marginBottom: 16 }}>
      <h3>{keepAlive ? "With Activity" : "With && (state is lost)"}</h3>
      <button onClick={() => setTab("maths")}>Maths</button>
      <button onClick={() => setTab("physics")}>Physics</button>
      <div>
        {keepAlive ? (
          <>
            <Activity mode={tab === "maths" ? "visible" : "hidden"}><NoteBox label="maths" /></Activity>
            <Activity mode={tab === "physics" ? "visible" : "hidden"}><NoteBox label="physics" /></Activity>
          </>
        ) : (
          <>
            {tab === "maths" && <NoteBox label="maths" />}
            {tab === "physics" && <NoteBox label="physics" />}
          </>
        )}
      </div>
    </section>
  );
}

createRoot(document.getElementById("root")).render(
  <>
    <Tabs keepAlive={false} />
    <Tabs keepAlive={true} />
  </>
);

The useState hook remembers which tab is open; you will study it properly in State with useState. In the first version, your maths notes disappear when you switch to physics and back. In the second, they are still there. Hidden activities still use memory, so use <Activity> for a few important panels, not for hundreds of items.

Common mistakes

  • Writing if inside JSX braces. {if (x) {...}} is a syntax error. Use a ternary, &&, or move the if above the return.
  • The 0 bug. {items.length && <List />} shows 0 for an empty list. Write {items.length > 0 && <List />}.
  • Deeply nested ternaries. a ? b : c ? d : e inside JSX is hard to read. Use if statements or a lookup object.
  • Returning undefined. Forgetting return in one branch makes the component return undefined. Return null on purpose when you want to show nothing.

Key points

  • Conditional rendering uses plain JavaScript: if, ? : and &&.
  • A component can return early with different JSX, or return null to render nothing.
  • Inside JSX braces only expressions work, so use the ternary or && there.
  • && renders its left side when that side is falsy, so 0 appears on screen; compare with > 0.
  • Removing a component throws away its state; <Activity mode="hidden"> hides UI and keeps it.

Exercise

Write a ResultCard component with props name and marks (0 to 100). Show "Distinction" for 75 and above, "Pass" for 33 to 74 and "Fail" below 33, each in a different colour. If marks is not given, show "Result awaited". Also show a small "Topper!" badge only when marks are 95 or more.

Show solution
React JSX
import { createRoot } from "react-dom/client";

function ResultCard({ name, marks }) {
  if (marks === undefined) {
    return <p>{name}: Result awaited</p>;
  }

  let grade = "Fail";
  let colour = "crimson";
  if (marks >= 75) {
    grade = "Distinction";
    colour = "green";
  } else if (marks >= 33) {
    grade = "Pass";
    colour = "#087ea4";
  }

  return (
    <p>
      {name}: {marks} <span style={{ color: colour }}>{grade}</span>
      {marks >= 95 && <strong> Topper!</strong>}
    </p>
  );
}

createRoot(document.getElementById("root")).render(
  <>
    <ResultCard name="Ananya" marks={97} />
    <ResultCard name="Kabir" marks={58} />
    <ResultCard name="Rohan" marks={21} />
    <ResultCard name="Isha" />
  </>
);

The early return handles the missing result, variables hold the grade and colour, and marks >= 95 && is a true boolean, so no stray number can appear.

Quick Quiz

1. What does {count && <p>New</p>} show when count is 0?

2. How can a component render nothing at all?

3. Why can you not write an if statement inside JSX curly braces?

4. What happens to an input's typed text when {show && <Form />} becomes false?

Learn React with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call