Conditional rendering
Show different UI for different data in React: if and early returns, the ternary, && without the stray 0, and returning null, in live examples you can edit.
- 01Choose with if and early returnsReturn different JSX for different data, and handle a special case, like an empty cart, first with an early return.
- 02Pick parts inside the JSXUse the ternary to choose one of two pieces and && to show a piece or nothing, without the stray 0 a number can leave.
- 03Decide where a condition livesSay what changes when a parent leaves a component out, when the component returns null, and when an element is only hidden.
One component, a different row for each case
Real pages change with their data. A product that's sold out shows "sold out" instead of a button. An empty cart says so. A sale banner appears only during a sale. Showing different things in different cases is called conditional rendering, and React has no special syntax for it: you use the JavaScript you already know. Here is a product list where one row is different. Its result is live under the code.
function ProductRow({ name, stock }) { if (stock === 0) { return <li>{name}: sold out</li>; } return ( <li> {name}: {stock} left <button>Add to cart</button> </li> );} export default function App() { return ( <ul> <ProductRow name="Tea" stock={5} /> <ProductRow name="Cake" stock={2} /> <ProductRow name="Lemonade" stock={0} /> </ul> );}ProductRow on line 1 takes two props, name and stock, from its tag on lines 15 to 17, as in Passing props to a component.
Line 2 is a plain if statement. When stock is 0, line 3 returns a short row that says sold out, and the function ends there. Otherwise the if is skipped, and lines 5 to 9 return the full row with the count and a button. App uses ProductRow three times, so the result shows Tea: 5 left and Cake: 2 left, each with a button, and Lemonade: sold out.
Press Edit & run and change Lemonade's stock={0} on line 17 to stock={3}. The result updates as you type: Lemonade gets 3 left and a button. You changed the data; the component chose the row.
stock is 0, so the if on line 2 is true and line 3 returns the sold-out row. For Tea and Cake it's false, and the return on lines 5 to 9 runs instead. One function, two possible rows.A café's chalkboard lists today's cakes. The owner looks in the kitchen and writes the board to match: a price for the cakes she has, "sold out" for the ones that are gone, and no line at all for the cakes she doesn't make today. She follows the same rules every time; only the kitchen changes.
- In real life: What's left in the kitchen right now
- In React: The props and data, like
stock - In real life: The rule: if a cake is gone, write "sold out"
- In React:
if (stock === 0) return <li>…sold out</li>; - In real life: Writing the board for what's true now
- In React: Your component returning JSX for the current data
- In real life: Leaving a line off the board
- In React: Returning
null, orfalsefrom&&, so React shows nothing there
Conditional rendering means choosing what a component returns with ordinary JavaScript: an if before the return, or the ternary ? : and && inside the JSX. Whatever the component returns for the current data is what React shows. When it returns null, or a spot in the JSX holds false, React shows nothing there.
Choosing with if, before the return
A component is a function, so everything a function can do before its return is open to you, including if. The most common shape is the early return: handle a special case first and return right away, so the rest of the function only deals with the usual case. A cart summary is a good fit, because an empty cart has nothing to add up:
function CartSummary({ items }) { if (items.length === 0) { return <p>Your cart is empty.</p>; } const total = items.reduce((sum, item) => sum + item.price, 0); return ( <p> {items.length} items, ${total} in total </p> );} const tea = { name: "Tea", price: 3 };const cake = { name: "Cake", price: 4 }; export default function App() { return ( <> <CartSummary items={[]} /> <CartSummary items={[tea, cake]} /> </> );}Line 2 checks for an empty cart. If items has no items, line 3 returns Your cart is empty. and CartSummary stops: a return ends the function, so line 6 never runs. For a cart with items, line 6 adds up the prices with reduce, and lines 7 to 11 return the summary. Line 20 passes an empty array and line 21 passes tea and cake, so the result shows Your cart is empty., then 2 items, $7 in total.
The if sits above the return, not inside the JSX. As JavaScript in JSX with curly braces showed, curly braces take only an expression, code that gives back a value, and if is a statement. Inside the braces, the ternary and && below do the choosing.
An if can also choose what goes in one part of the markup. JSX is a value, a small object that describes an element, so you can store it in a variable, change it with if, and put the variable in the braces:
function OrderStatus({ id, status }) { let message = <span>We're packing it.</span>; if (status === "shipped") { message = <span>It's on its way.</span>; } else if (status === "delivered") { message = <strong>Delivered.</strong>; } return <li>Order {id}: {message}</li>;} export default function App() { return ( <ul> <OrderStatus id={7} status="packing" /> <OrderStatus id={8} status="shipped" /> <OrderStatus id={9} status="delivered" /> </ul> );}Line 2 makes message with the usual case, a <span> saying We're packing it. It uses let, so lines 4 and 6 can replace it: with "on its way" when the order has shipped, or a <strong> when it's delivered. Line 8 puts whichever one is left into the row with {message}. The result shows three orders, 7, 8, and 9, one in each state.
This is the most flexible shape, and the one to reach for when there are three or more cases, or when each case needs a few lines of its own. The <li> around the message is written once, on line 8, whatever the status.
The ternary: one of two, inside the JSX
The overview's ProductRow has two return statements, and both build an <li>. When only a small part changes, you can write the row once and choose just that part with the ternary operator: condition ? a : b gives back a when the condition is true and b when it's false. It's an expression, so it fits inside curly braces, and in an attribute's value too:
function ProductRow({ name, stock }) { return ( <li className={stock > 0 ? "in-stock" : "sold-out"}> {name}: {stock > 0 ? <button>Add to cart</button> : <em>sold out</em>} </li> );} export default function App() { return ( <ul> <ProductRow name="Tea" stock={5} /> <ProductRow name="Cake" stock={2} /> <ProductRow name="Lemonade" stock={0} /> </ul> );}Line 3 picks the row's class: "in-stock" when stock > 0, otherwise "sold-out". The styles.css tab makes in-stock rows bold and fades sold-out ones. Line 4 picks the end of the row: a button, or <em>sold out</em>. Read it as "if stock > 0, a button, otherwise sold out". Tea and Cake get buttons, and Lemonade's row is faded and says sold out.
Is this different from writing two returns? No. JSX elements are descriptions, not pieces of the page, so both versions hand React the same description for the same data, and the page ends up the same. Choose whichever reads better: two returns when the cases are very different, a ternary when they share most of the markup.
A ternary inside another ternary works, but it gets hard to read quickly:
<p>
{stock === 0 ? "Sold out" : stock < 3 ? "Only a few left" : "In stock"}
</p>Line 2 reads: if stock is 0, Sold out; otherwise, if it's below 3, Only a few left; otherwise In stock. It's a fragment of a component, so it has no result of its own. The same three cases read more easily as if statements with a variable, before the return:
function StockLabel({ stock }) { let label = "In stock"; if (stock === 0) { label = "Sold out"; } else if (stock < 3) { label = "Only a few left"; } return <p>{label}</p>;} export default function App() { return ( <> <StockLabel stock={5} /> <StockLabel stock={2} /> <StockLabel stock={0} /> </> );}Line 2 starts with the usual case, and lines 3 to 7 replace it for the two special ones. Line 8 shows the result. For stock 5, 2, and 0, the result reads In stock, Only a few left, and Sold out.
&& for something or nothing, and the stray 0
Often you want a piece of markup in one case and nothing in the other: a note for members, a free-delivery line over $20. A ternary with null on one side works, and && says it more briefly:
function Checkout({ total, isMember }) { return ( <section> <p>Total: ${total}</p> {isMember && <p>Members get free delivery.</p>} {total >= 20 && <p>Your order ships free.</p>} </section> );} export default function App() { return ( <> <Checkout total={12} isMember={true} /> <Checkout total={25} isMember={false} /> </> );}Read line 5 as "if isMember, then this paragraph". Here is how it works. a && b looks at a first. If a is falsy, it stops and gives back a itself. If a is truthy, it gives back b. So for a member, line 5 gives back the <p>; for anyone else it gives back false. React draws nothing for false, true, null, or undefined, so that spot stays empty. Line 6 does the same with a comparison: total >= 20.
Line 14 is a member with a $12 order, and line 15 isn't a member but spends $25. The result shows Members get free delivery. under the first total and Your order ships free. under the second.
There's one trap. "Gives back a itself" matters when a is a number, because React does show numbers, including 0. Here a cart link shows its count in brackets, with a bug on purpose:
function CartLink({ count }) { return ( <p> Cart {count && <strong>({count})</strong>} </p> );} export default function App() { return ( <> <CartLink count={2} /> <CartLink count={0} /> </> );}The first link says Cart (2), as it should. The second says Cart 0. On line 4, count && … with a count of 0 stops at the falsy 0 and gives back 0, not false, and React shows it. NaN, the "not a number" value, does the same. An empty string "" is given back too, but it shows as empty text, so you can't see it.
&& gives back its left side when that side is falsy. For 0, that's the number 0, and React shows numbers. Compare first: 0 > 0 is false, and React draws nothing for false.The fix is to give && a true-or-false test. Press Edit & run and change line 4 to {count > 0 && <strong>({count})</strong>}. 0 > 0 is false, so the second link reads just Cart. A ternary, {count > 0 ? <strong>…</strong> : null}, works too. The JavaScript you need for React has a playground that shows what JSX does with every kind of value.
function CartLink({ count }) { return ( <p> Cart {count > 0 && <strong>({count})</strong>} </p> );} export default function App() { return ( <> <CartLink count={2} /> <CartLink count={0} /> </> );}Returning null, and where the condition lives
A component can return null, which means "nothing to show". So a component can decide for itself to show nothing. Here a sale banner returns null when there's no sale:
function SaleBanner({ onSale }) { if (!onSale) { return null; } return <p className="banner">Sale: 20% off tea</p>;} function ProductPage({ onSale }) { return ( <div> <h3>Green tea</h3> <SaleBanner onSale={onSale} /> <p>$3</p> </div> );} export default function App() { return ( <> <ProductPage onSale={true} /> <ProductPage onSale={false} /> </> );}Lines 2 to 4 return null when onSale is false, so SaleBanner shows nothing. Otherwise line 5 returns the banner. Line 12 uses SaleBanner inside ProductPage and passes onSale along. Lines 21 and 22 show the page twice: the first with the banner under the heading, the second without it.
The same page could leave the banner out from the parent instead: {onSale && <SaleBanner />} in ProductPage, with a SaleBanner that always shows. The page looks the same. What differs is what React gets. Try all four ways below; onSale switches the sale on and off, and the result pane shows what ProductPage returns, whether React calls SaleBanner, and the real HTML on the page.
function SaleBanner() { return <p className="banner">Sale: 20% off tea</p>;} function ProductPage({ onSale }) { return ( <div> <h3>Green tea</h3> {onSale && <SaleBanner />} <p>$3</p> </div> );}onSale={false}false && … gives back false, so ProductPage's description holds false where the banner was. React draws nothing for it and never calls SaleBanner.
Start with && in ProductPage and onSale={false}. The <div> holds <h3>, false, and <p>, and SaleBanner is "Not called": React calls only the components in the description. Pick SaleBanner returns null: the description now holds <SaleBanner onSale={false} />, so React calls it, and it returns null. The HTML is the same both times.
onSale false, both pages look the same. On the left, ProductPage's description holds false, and React never calls SaleBanner. On the right, it holds a <SaleBanner>, so React calls it, and it returns null.Which to choose? Usually the parent: {onSale && <SaleBanner />} says right where the banner is used that it might not show. Returning null suits a component that knows better than its parents when it has nothing to say, like a warning that checks its own numbers. Either way, prefer null to undefined. Since React 18, a component that returns undefined also shows nothing, without an error, so a forgotten return fails silently; return null says you meant it.
Not rendered means removed
Now pick the hidden attribute with onSale={false}. The HTML still holds the banner's <p>, with hidden: it's on the page, and the browser doesn't draw it. With the other three ways, the element isn't there at all. That difference matters as soon as the element holds something the reader typed:
import { useState } from "react"; export default function App() { const [wantsNote, setWantsNote] = useState(false); return ( <> <label> <input type="checkbox" checked={wantsNote} onChange={(event) => setWantsNote(event.target.checked)} /> Add a gift note </label> {wantsNote && <textarea placeholder="Your note" />} </> );}Line 4 keeps wantsNote in state, a value React remembers for the component, starting as false. Ticking the box on lines 9 to 13 sets it, and React shows the component again (state gets its own lesson, State: a component's memory). Line 16 shows the <textarea> only while wantsNote is true.
Tick the box, type Happy birthday, Asha, untick it, and tick it again. The box is empty. When wantsNote became false, React removed the <textarea> from the page, and your text went with it; ticking again made a new, empty one. Now press Edit & run and change line 16 to <textarea placeholder="Your note" hidden={!wantsNote} />. Try again: the note is still there, because the element never left the page. Remove what you don't need; hide what has to keep its contents.
An order history with every kind of condition
A real page mixes all of these. Here is an order history: each order shows its status, a gift label when it's a gift, and a button that depends on where the order is.
const orders = [ { id: 7, item: "Tea", status: "delivered", gift: false }, { id: 8, item: "Cake", status: "shipped", gift: true }, { id: 9, item: "Lemonade", status: "packing", gift: false },]; function OrderActions({ status }) { if (status === "packing") { return <button>Cancel</button>; } if (status === "shipped") { return <button>Track</button>; } return null;} function OrderRow({ order }) { const done = order.status === "delivered"; return ( <li> #{order.id} {order.item}: {done ? <strong>delivered</strong> : order.status} {order.gift && <em> (gift)</em>} <OrderActions status={order.status} /> </li> );} export default function App() { if (orders.length === 0) { return <p>No orders yet.</p>; } return ( <ul> {orders.map((order) => ( <OrderRow key={order.id} order={order} /> ))} </ul> );}Each kind of condition has a job:
- Lines 28 to 30 are an early return in
App: with no orders, the page saysNo orders yet.and stops. - Lines 7 to 15 are
OrderActions, which decides for itself: a Cancel button while packing, Track once shipped, andnullafter delivery, when there's nothing to do. - Line 21's ternary bolds
deliveredand shows the other statuses as they are. Line 22's&&adds(gift)only whenorder.giftis true; it's a true-or-false value, so no stray 0.
The result shows #7 Tea: delivered with no button, #8 Cake: shipped (gift) with Track, and #9 Lemonade: packing with Cancel. Press Edit & run and replace lines 1 to 5 with const orders = [];. The result shows No orders yet. Lines 33 to 35 turn the array into rows with map and give each a key; the next lesson, Rendering lists, is all about that.
How to read this table: each row is something you want the page to do, what to write, and why it works.
| You want | Write | Why |
|---|---|---|
| A different screen for a special case, like an empty cart | if (items.length === 0) return <p>…</p>; | The early return handles the case first, so the rest of the function only deals with the usual case. |
| One of two pieces inside the markup | {inStock ? <button>…</button> : <em>sold out</em>} | The ternary is an expression, so it fits in curly braces, and both pieces share the markup around them. |
| A piece or nothing | {isMember && <p>…</p>} | With true or false on the left, && gives back the element or false, and React draws nothing for false. |
| A piece or nothing, based on a number | {count > 0 && <strong>…</strong>} | The comparison turns the number into true or false, so a 0 can't reach the page. |
| Three or more cases | if and else if that set a variable before the return | A ternary inside a ternary is hard to read. Plain if statements stay clear. |
| A component that decides for itself | if (!onSale) return null; | React still calls the component, and draws nothing for null. |
| Out of sight, but keep what was typed | hidden={!wantsNote} | The element stays on the page, only hidden, so the text in it stays too. |
{count && <Badge />}, wherecountis a number{count > 0 && <Badge />}, wherecountis a number{items.length && <List />}, whereitemsis an array{items.length > 0 && <List />}, whereitemsis an array{user && <Greeting />}, whereuseris an object ornull{price && <Price />}, wherepriceis a number, and can beNaN{count ? <Badge /> : null}, wherecountis a number{name && <Greeting />}, wherenameis a string
Each card is an expression in JSX braces, with what its variable holds. Sort it: does it show only the element or nothing, or can it show a stray 0 or NaN?
Common mix-ups
- "React has a special tag for conditions." No. Some other libraries have one, like Vue's
v-if. React uses plain JavaScript:if,? :, and&&. - "A component must always return some JSX." No. It can return
nullto show nothing, asSaleBannerandOrderActionsdo. - "When the left side of && is false, nothing shows." Only when it's
false,null,undefined, or an empty string. A0orNaNshows. Compare first:count > 0 && …. - "A component that returns null is skipped." No. React still calls it; it just shows nothing. To keep React from calling it, leave it out in the parent.
- "Conditional rendering hides an element." No. It removes the element from the page, with anything typed into it. To keep it and only hide it, use the
hiddenattribute or CSS. - "Two returns make different elements than one return with a ternary." No. JSX describes elements; for the same data both versions describe the same thing, and the page ends up the same.
Practice exercises
Each exercise shows its result under the code, or when you press Edit & run. Change the code, and the result updates as you type. For the first two, answer before you run anything.
What shows under the Inbox heading? Answer first, then press Edit & run to check.
function Inbox({ messages }) {
return (
<section>
<h3>Inbox</h3>
{messages.length && <p>You have {messages.length} new messages.</p>}
</section>
);
}
export default function App() {
return <Inbox messages={[]} />;
}function Inbox({ messages }) { return ( <section> <h3>Inbox</h3> {messages.length && <p>You have {messages.length} new messages.</p>} </section> );} export default function App() { return <Inbox messages={[]} />;}messages.length is 0, so line 5's && gives back 0, and React shows it under the heading. Write messages.length > 0 && to show nothing instead.
What does the page say for a visitor who isn't signed in?
function Greeting({ user }) {
if (!user) {
return <p>Sign in to see your orders.</p>;
}
return <p>Welcome back, {user.name}!</p>;
}
export default function App() {
return <Greeting user={null} />;
}function Greeting({ user }) { if (!user) { return <p>Sign in to see your orders.</p>; } return <p>Welcome back, {user.name}!</p>;} export default function App() { return <Greeting user={null} />;}null is falsy, so !user is true and the early return on line 3 runs. That's also why line 5 is safe: it reads user.name only when there is a user.
The second link says Cart 0. Fix line 4 so an empty cart shows no number. What does the second line say then?
function CartLink({ count }) { return ( <p> Cart {count && <strong>({count})</strong>} </p> );} export default function App() { return ( <> <CartLink count={2} /> <CartLink count={0} /> </> );}function CartLink({ count }) { return ( <p> Cart {count > 0 && <strong>({count})</strong>} </p> );} export default function App() { return ( <> <CartLink count={2} /> <CartLink count={0} /> </> );}count > 0 is false for a count of 0, and React draws nothing for false, so the second link reads just Cart. The first still says Cart (2).
Rewrite Delivery with a single return, so the <li> is written once. Keep the result the same. How many return statements does Delivery have now?
function Delivery({ name, express }) { if (express) { return <li>{name}: arrives tomorrow</li>; } return <li>{name}: arrives in 3 days</li>;} export default function App() { return ( <ul> <Delivery name="Tea" express={true} /> <Delivery name="Cake" express={false} /> </ul> );}function Delivery({ name, express }) { return <li>{name}: arrives {express ? "tomorrow" : "in 3 days"}</li>;} export default function App() { return ( <ul> <Delivery name="Tea" express={true} /> <Delivery name="Cake" express={false} /> </ul> );}The ternary picks only the words that change, so the <li> is written once. The result is the same as before: Tea: arrives tomorrow and Cake: arrives in 3 days.
Write a LowStockWarning component that shows a warning like Only 2 left! when there are 1 or 2 left, and nothing otherwise, even for sold-out products. Use it in every ProductCard. How many warnings show?
// Write LowStockWarning here. function ProductCard({ name, stock }) { return ( <li> {name} </li> );} export default function App() { return ( <ul> <ProductCard name="Tea" stock={5} /> <ProductCard name="Cake" stock={2} /> <ProductCard name="Lemonade" stock={0} /> </ul> );}function LowStockWarning({ stock }) { if (stock === 0 || stock >= 3) { return null; } return <strong> Only {stock} left!</strong>;} function ProductCard({ name, stock }) { return ( <li> {name} <LowStockWarning stock={stock} /> </li> );} export default function App() { return ( <ul> <ProductCard name="Tea" stock={5} /> <ProductCard name="Cake" stock={2} /> <ProductCard name="Lemonade" stock={0} /> </ul> );}LowStockWarning knows its own rule, so ProductCard can always use it. For 5 and 0 it returns null; for Cake's 2 it returns the warning, so one shows: Cake Only 2 left!.
reviews is null while the reviews load, and [] when there are none. Make ReviewList show Loading reviews… and No reviews yet. for those cases. What does the second ReviewList show?
function ReviewList({ reviews }) { // reviews is null while loading, [] when there are none. return <p>{reviews?.length} reviews</p>;} export default function App() { return ( <> <ReviewList reviews={null} /> <ReviewList reviews={[]} /> <ReviewList reviews={["Lovely tea", "Fresh cake"]} /> </> );}function ReviewList({ reviews }) { if (reviews === null) { return <p>Loading reviews…</p>; } if (reviews.length === 0) { return <p>No reviews yet.</p>; } return <p>{reviews.length} reviews</p>;} export default function App() { return ( <> <ReviewList reviews={null} /> <ReviewList reviews={[]} /> <ReviewList reviews={["Lovely tea", "Fresh cake"]} /> </> );}Each special case returns early: null shows Loading reviews…, an empty array shows No reviews yet., and only real reviews reach line 8. The null check comes first, because null.length throws.
Check your understanding
For each code question, find the data first, then follow the condition. Press Edit & run on a question's code to check your answer.
Question 1 of 7What does this component show for
unread={0}?Read the code, then predictfunction Messages({ unread }) { return <p>Messages {unread && <b>({unread})</b>}</p>; } export default function App() { return <Messages unread={0} />; }Choose an answer to see the explanation.
Question 2 of 7A label has three cases: sold out, only a few left, and in stock. Which way does this lesson suggest?
Choose an answer to see the explanation.
Question 3 of 7What does
Shippingshow forstatus="shipped"?Read the code, then predictfunction Shipping({ status }) { if (status === "lost") { return <p>Please contact us.</p>; } if (status === "shipped") { return <p>On its way.</p>; } return <p>Packing.</p>; } export default function App() { return <Shipping status="shipped" />; }Choose an answer to see the explanation.
Question 4 of 7What class does Lemonade's
<li>get?Read the code, then predictfunction ProductRow({ name, stock }) { return <li className={stock > 0 ? "in-stock" : "sold-out"}>{name}</li>; } export default function App() { return ( <ul> <ProductRow name="Lemonade" stock={0} /> </ul> ); }Choose an answer to see the explanation.
Question 5 of 7
ProductPagewrites{onSale && <SaleBanner />}, andonSaleis false. What does React do withSaleBanner?Choose an answer to see the explanation.
Question 6 of 7In the gift note example, you tick the box, type a note, untick it, and tick it again. What's in the box?
Choose an answer to see the explanation.
Question 7 of 7You want a badge only when
items.lengthis above 0. Which line is safe?Choose an answer to see the explanation.
Key takeaways
- React has no special syntax for conditions. A component chooses what to return with plain JavaScript.
- Use
ifbefore thereturnfor whole different results, and early returns for special cases like an empty cart. - Inside the JSX,
a ? b : cpicks one of two pieces, anda && bshows a piece or nothing. Give&&a true-or-false test, likecount > 0, or a0shows up. - React shows nothing for
null,false,true, andundefined. A component can returnnull, but React still calls it; leaving it out in the parent means React never does. - A condition that turns false removes the element from the page, with anything typed into it. The
hiddenattribute only hides it.
Remember the one-liner.
The component decides with plain JavaScript; React shows whatever it returns, and nothing for null and false.
Coming next: Rendering lists. It turns arrays of data into lists of components with map and filter, and shows why each item needs a key.