React Basics #7: Conditional Rendering
Last time we covered event handling and saw a glimpse of expressions like {lastSubmitted && ...} at the end. This time we’ll round up the patterns for conditional rendering — drawing the screen differently based on state.
What is conditional rendering? #
Showing different menus depending on whether the user is logged in, displaying a loading indicator while data is being fetched, or showing an error message when input validation fails — every app does this. Rendering different JSX based on some condition is what we call conditional rendering.
React doesn’t have a special syntax for this. You just use JavaScript’s conditional expressions. Let’s look at the four most common patterns.
Pattern 1. Branching with if — early return
#
The most straightforward way is to branch with if inside the component function and return different JSX.
function Greeting({ user }) {
if (!user) {
return <p>You need to log in.</p>;
}
return <h1>Hello, {user.name}!</h1>;
}
export default Greeting;If user is missing, 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’s readable when the branches are large.
Pattern 2. Ternary operator — picking between two in JSX #
When you need to choose between two things 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’s curly braces? An if statement is a statement, so it can’t go there, but the ternary operator is an expression, so it can.
You can also use it to split JSX itself into two branches.
function UserStatus({ user }) {
return (
<div>
{user ? (
<p>Hello, {user.name}!</p>
) : (
<p>You need to log in.</p>
)}
</div>
);
}When the ternary gets long, readability drops, so for big chunks of JSX it’s better to use Pattern 1 (early return) or to split things into variables.
Pattern 3. The && operator — show or hide
#
The most common case is “show this only when the condition is true; show nothing otherwise.” && fits this nicely.
function Notification({ unreadCount }) {
return (
<div>
<h2>Notifications</h2>
{unreadCount > 0 && (
<p>You have {unreadCount} unread messages.</p>
)}
</div>
);
}
export default Notification;In JavaScript, A && B returns B if A is truthy and A if it’s falsy. If unreadCount > 0 is true, <p>...</p> ends up there; if it’s false, false ends up there. React doesn’t render anything for false, null, or undefined, so it looks like nothing is there.
A pitfall with && — the number 0
#
There’s a common pitfall with &&. Look at this code.
function Cart({ count }) {
return (
<div>
{count && <p>{count} items in your cart.</p>}
</div>
);
}When count is 0, the intent is “show nothing,” but in reality the screen displays the number 0. That’s 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 convert it to a boolean.
{count > 0 && <p>{count} items in your cart.</p>}count > 0 is always true or false, so it’s safe. Get into the habit of putting a clear boolean on the left side of && and you won’t fall into this trap.
Pattern 4. Returning null — don’t render the component at all
#
When the entire component should disappear from the screen if a condition isn’t 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 at the component’s position. From the parent’s perspective, you can always write <Banner message={...} />, and Banner itself decides whether to appear. Clean.
Storing JSX in a variable #
When the branching gets complex, it reads better to 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 store it in an object.
Pattern summary #
| Situation | Recommended pattern |
|---|---|
| The branch decides the entire JSX of the component | early return (if) |
| Pick one of two things to show | ternary operator (A ? B : C) |
| Show only when the condition is true | && operator (boolean on the left) |
| Hide the component entirely | return null |
| Three or more branches, or complex branching | store JSX in a variable |
The key point is that there’s no special React syntax — you just use JavaScript’s conditional expressions inside JSX.
Try it yourself #
Let’s extend the MessageForm from last article a bit: “show a warning if the name is empty,” “show a warning if the message is empty,” and “submit only when both are filled in.”
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 submitted: <strong>{lastSubmitted.name}</strong> — {lastSubmitted.message}
</p>
) : (
<p style={{ marginTop: '12px', color: '#888' }}>
No messages submitted yet.
</p>
)}
</div>
);
}
export default MessageForm;Conditional rendering patterns used here:
name.length === 0 && <span>...</span>— show a warning only when nothing is typed (&&)disabled={!isValid}— enable/disable the button based on validityisValid ? 'Add' : 'Please complete the form'— branch the button label with a ternarylastSubmitted ? <p>...</p> : <p>...</p>— branch the info message with a ternary
Multiple patterns naturally mix in one screen. Type around in the form and observe how the screen reacts.
Wrapping up #
In this article we looked at the four conditional rendering patterns (early return, ternary, &&, returning null) and the variable trick. There’s no single right answer — what matters is the sense to pick the most readable pattern for the situation. And just dodge the number 0 pitfall when using &&.
So far the data we’ve drawn has been limited to one or two values. But real apps have to render many pieces of data at once — lists of posts, products, notifications. In the next article, “React Basics #8: Lists and key,” we’ll cover how to render arrays on screen and the meaning of the special prop key that always shows up alongside.