Conditional rendering
Patterns for splitting the UI with if · ternary · && · null returns, plus common traps — especially the trap of the number 0 on the left of `&&`.
At the end of Chapter 6 we briefly saw an expression like {lastSubmitted && ...}. In this chapter we sort out conditional rendering patterns for drawing the screen differently based on state.
What is conditional rendering? #
Showing a different menu based on login state, displaying a loading indicator while data is fetching, or surfacing an error message when input validation fails — all of these happen in every app. Rendering different JSX based on some condition is called conditional rendering.
React does not have any special syntax for this; it uses JavaScript’s conditional expressions as-is. Let us look at the four patterns you will use most often.
Pattern 1. Branch with if (early return)
#
The most straightforward approach is to branch with if inside the component function and return different JSX.
function Greeting({ user }) {
if (!user) {
return <p>Please log in.</p>;
}
return <h1>Hello, {user.name}!</h1>;
}
export default Greeting;If user is absent, return the login prompt and the function ends. The second return only runs when user exists. This style of exiting a function early is called early return, and it reads well when the branches are large.
Pattern 2. Ternary operator (two-way pick inside JSX) #
When you need to pick one of two things right in the middle of JSX, use JavaScript’s ternary operator (condition ? A : B).
function LoginButton({ isLoggedIn }) {
return (
<button>
{isLoggedIn ? 'Log out' : 'Log in'}
</button>
);
}
export default LoginButton;Remember that only expressions can go inside JSX curly braces? if is a statement and cannot, but the ternary is an expression and can.
It also works for splitting JSX itself two ways.
function UserStatus({ user }) {
return (
<div>
{user ? (
<p>Hello, {user.name}!</p>
) : (
<p>Please log in.</p>
)}
</div>
);
}But as ternaries grow, readability drops. When the JSX chunks get large, Pattern 1 (early return) or pulling JSX into a variable reads better.
Pattern 3. && (show or don’t show)
#
The most common case is “show something only when the condition is true, otherwise show nothing.” For that, the && operator is a good fit.
function Notification({ unreadCount }) {
return (
<div>
<h2>Notifications</h2>
{unreadCount > 0 && (
<p>You have {unreadCount} unread message(s).</p>
)}
</div>
);
}
export default Notification;In JavaScript, A && B returns B when A is truthy and A when A is falsy. When unreadCount > 0 is true, <p>...</p> lands in that spot; when false, false lands there. React renders nothing for false, null, and undefined, so it ends up looking as if the element disappeared.
The && trap — the number 0
#
There is a common trap with &&. Look at the following code.
function Cart({ count }) {
return (
<div>
{count && <p>{count} item(s) in your cart.</p>}
</div>
);
}When count is 0, the intent is “show nothing,” but in practice the number 0 itself shows up on the screen. That is because 0 && X returns 0, and React really does render the number 0 (only false, null, and undefined are skipped).
The fix is to explicitly coerce to a boolean.
{count > 0 && <p>{count} item(s) in your cart.</p>}count > 0 is always true or false, which is safe. Get in the habit of putting a clear boolean on the left of && and you will not fall into this trap. The empty string '' has the same problem, so instead of name && ..., write something explicit like name.length > 0 && ....
Pattern 4. Return null (don’t render the component at all)
#
When the component itself should disappear from the screen if a condition is not met, return null.
function Banner({ message }) {
if (!message) return null;
return (
<div style={{ background: '#fffbcc', padding: '12px' }}>
{message}
</div>
);
}
export default Banner;When you return null, React renders nothing in that spot. From the parent’s perspective, you always write <Banner message={...} /> and the decision of whether to show is left to Banner itself. Clean.
Storing JSX in a variable #
When the branching gets complex, reading improves if you store JSX in a variable and use it.
function Page({ status, data, error }) {
let content;
if (status === 'loading') {
content = <p>Loading...</p>;
} else if (status === 'error') {
content = <p style={{ color: 'red' }}>Error: {error}</p>;
} else {
content = <p>Data: {data}</p>;
}
return (
<div>
<h1>Page title</h1>
{content}
</div>
);
}JSX is just a JavaScript value, so you can put it in a variable, return it from a function, or place it inside an object.
Patterns at a glance #
| Situation | Recommended pattern |
|---|---|
| The branch result is the component’s whole JSX | early return (if) |
| You want to show one of two things | ternary (A ? B : C) |
| You want to show something only when true | && (boolean on the left) |
| The component should not appear at all | return null |
| Three or more branches, or it gets complex | store JSX in a variable |
The key point is that React does not have a special syntax — you use JavaScript’s conditional expressions as-is inside JSX.
Try it yourself #
Let us extend the MessageForm from Chapter 6: show a warning if the name is empty, show a warning if the message is empty, and only allow submission when both are valid.
import { useState } from 'react';
function MessageForm() {
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const [lastSubmitted, setLastSubmitted] = useState(null);
const isValid = name.length > 0 && message.length > 0;
function handleSubmit(e) {
e.preventDefault();
if (!isValid) return;
setLastSubmitted({ name, message });
setName('');
setMessage('');
}
return (
<div style={{ padding: '16px', border: '1px solid #ccc', borderRadius: '8px' }}>
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
{name.length === 0 && (
<span style={{ color: 'red', marginLeft: '8px' }}>Please enter a name</span>
)}
</div>
<div style={{ marginTop: '8px' }}>
<input
type="text"
placeholder="Message"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
{message.length === 0 && (
<span style={{ color: 'red', marginLeft: '8px' }}>Please enter a message</span>
)}
</div>
<button type="submit" disabled={!isValid} style={{ marginTop: '8px' }}>
{isValid ? 'Add' : 'Please complete the form'}
</button>
</form>
{lastSubmitted ? (
<p style={{ marginTop: '12px' }}>
Last submission: <strong>{lastSubmitted.name}</strong>. {lastSubmitted.message}
</p>
) : (
<p style={{ marginTop: '12px', color: '#888' }}>
No message submitted yet.
</p>
)}
</div>
);
}
export default MessageForm;Conditional rendering patterns used here:
name.length === 0 && <span>...</span>—&&shows the warning only when the input is emptydisabled={!isValid}— enable / disable the button based on validityisValid ? 'Add' : 'Please complete the form'— ternary for switching button textlastSubmitted ? <p>...</p> : <p>...</p>— ternary for the status message
An example where several patterns mix naturally in one screen.
Exercises #
- Build a
Pagecomponent that renders differently based on astatusprop ('loading'/'error'/'success').'loading'shows “Loading”,'error'shows a red error message,'success'shows the data. Write it three ways — early return / ternary / variable-with-JSX — and compare which reads most easily. - Hit the
&&trap yourself. In a counter starting from 0, use{count && <p>Cart: {count}</p>}. Initially the screen shows0against your intent. Then fix it withcount > 0 && ...to escape the trap. - Build a
Bannercomponent that shows a yellow box whenmessageis given and returnsnullotherwise. Always render it in the parent as<Banner message={errorMessage} />, and confirm that it naturally appears and disappears aserrorMessagestate oscillates between empty and not.
In one line: Conditional rendering in React is just JavaScript’s conditional expressions. early return / ternary /
&&/nullreturn / JSX in a variable — pick from the five patterns based on the situation. Keep a clear boolean on the left of&&to avoid the number 0 trap.
Next chapter #
So far the data we show has been limited to one or two pieces. In real apps we have to render multiple pieces of data at once — a post list, a product list, a notification list. In the next chapter, Chapter 8: Lists and key, we cover how to render an array on screen, and the meaning of a special prop, key, that inevitably appears. This becomes the foundation for the reconciliation algorithm discussion in Chapter 14 (Performance).