Not all content should be visible all the time. Sometimes you want to show a loading spinner, other times an error message, and when everything's ready, the actual data. This is called conditional rendering - displaying different UI based on certain conditions. It's one of the most important concepts in React, and in this lesson, you'll learn all the techniques to master it.
What is Conditional Rendering?
Conditional rendering means showing or hiding parts of your UI based on conditions - just like an if statement in regular programming. Instead of always rendering everything, you decide what to render based on:
- User authentication status (logged in or not)
- Data loading state (loading, error, or success)
- User permissions (admin vs regular user)
- Form validation (valid or invalid input)
- Feature flags (new feature enabled or not)
- Device type (mobile vs desktop)
- Time of day (morning vs evening greetings)
In React, components can return different JSX based on conditions, making your UI dynamic and responsive to the application state.
Method 1: If/Else Statements
The most straightforward way to conditionally render is using regular if/else statements before the return statement:
Basic If/Else
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>
}
return <h1>Please sign in</h1>
}
// Usage:
<Greeting isLoggedIn={true} /> // Shows: Welcome back!
<Greeting isLoggedIn={false} /> // Shows: Please sign inIf/Else with Multiple Conditions
function UserDashboard({ user }) {
if (!user) {
return <div>Loading user data...</div>
}
if (user.role === 'admin') {
return (
<div>
<h1>Admin Dashboard</h1>
<AdminPanel />
</div>
)
}
if (user.role === 'moderator') {
return (
<div>
<h1>Moderator Dashboard</h1>
<ModeratorPanel />
</div>
)
}
// Default case - regular user
return (
<div>
<h1>User Dashboard</h1>
<UserPanel />
</div>
)
}Early Returns
A common pattern is using "early returns" to handle edge cases first:
function ProductList({ products, isLoading, error }) {
// Handle loading state
if (isLoading) {
return <LoadingSpinner />
}
// Handle error state
if (error) {
return (
<div className="error">
<p>Error: {error.message}</p>
<button onClick={retry}>Try Again</button>
</div>
)
}
// Handle empty state
if (!products || products.length === 0) {
return <p>No products found</p>
}
// Happy path - show the products
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}π‘ When to Use If/Else
Use if/else statements when:
β’ You're returning completely different components
β’ You have multiple conditions to check
β’ The logic is complex and needs multiple lines
β’ You want to use early returns for edge cases
Method 2: Ternary Operator
The ternary operator (condition ? true : false) is perfect for conditional rendering inside your JSX:
Basic Ternary
function LoginButton({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<button>Logout</button>
) : (
<button>Login</button>
)}
</div>
)
}
// Inline version for simple cases
function Status({ isOnline }) {
return <span>{isOnline ? 'π’ Online' : 'β« Offline'}</span>
}Ternary for Elements vs Text
function MessageBox({ hasMessages, messageCount }) {
return (
<div>
{/* Conditional element rendering */}
{hasMessages ? (
<div className="messages">
<h3>You have {messageCount} new messages</h3>
<MessageList />
</div>
) : (
<div className="no-messages">
<p>No new messages</p>
</div>
)}
{/* Conditional text */}
<p>
Inbox: {messageCount > 0 ? `${messageCount} unread` : 'Empty'}
</p>
</div>
)
}Nested Ternaries (Use Sparingly!)
function ScoreGrade({ score }) {
return (
<div>
{/* β οΈ This works but gets hard to read */}
Grade: {
score >= 90 ? 'A' :
score >= 80 ? 'B' :
score >= 70 ? 'C' :
score >= 60 ? 'D' : 'F'
}
</div>
)
}
// β
Better: Use a function
function ScoreGrade({ score }) {
const getGrade = (score) => {
if (score >= 90) return 'A'
if (score >= 80) return 'B'
if (score >= 70) return 'C'
if (score >= 60) return 'D'
return 'F'
}
return <div>Grade: {getGrade(score)}</div>
}Avoid Deeply Nested Ternaries
While you can nest ternary operators, it quickly becomes unreadable. If you have more than two conditions, use if/else statements or extract the logic to a function.
π‘ Tip: Edit the code above and click "Run" to see your changes
Method 3: Logical AND Operator (&&)
The logical AND operator (&&) is perfect when you want to render something only if a condition is true, with nothing rendered if it's false:
Basic && Operator
function Notifications({ hasNotifications, count }) {
return (
<div>
<h1>My App</h1>
{/* Only show if hasNotifications is true */}
{hasNotifications && (
<div className="notification-badge">
{count} new notifications
</div>
)}
</div>
)
}
// If hasNotifications is false, nothing renders
// If hasNotifications is true, the div rendersMultiple Conditions with &&
function UserProfile({ user, isOwner, isPremium }) {
return (
<div>
<h1>{user.name}</h1>
{/* Show edit button only if user is owner */}
{isOwner && <button>Edit Profile</button>}
{/* Show premium badge only if user is premium */}
{isPremium && <span className="badge">β Premium</span>}
{/* Show admin panel only if both conditions true */}
{isOwner && isPremium && (
<div className="admin-panel">
<h3>Premium Owner Features</h3>
</div>
)}
</div>
)
}Common Patterns
function DataDisplay({ data, isLoading, error }) {
return (
<div>
{/* Show loading spinner while loading */}
{isLoading && <Spinner />}
{/* Show error message if there's an error */}
{error && (
<div className="error">
Error: {error.message}
</div>
)}
{/* Show data only if it exists and we're not loading */}
{!isLoading && data && (
<div className="data">
{data.map(item => <Item key={item.id} {...item} />)}
</div>
)}
{/* Show empty state if no data and not loading */}
{!isLoading && !data && (
<p>No data available</p>
)}
</div>
)
}Watch Out for Falsy Values!
Be careful with && when the left side could be 0 or "":{count && <div>{count} items</div>}
If count is 0, it will render "0" instead of nothing!
Use: {count > 0 && <div>{count} items</div>}
Or: {!!count && <div>{count} items</div>}
Method 4: Logical OR Operator (||)
The logical OR operator (||) is useful for providing fallback or default values:
function UserGreeting({ username, displayName }) {
return (
<div>
{/* Use displayName if it exists, otherwise use username */}
<h1>Hello, {displayName || username}!</h1>
{/* Use custom message or default */}
<p>{user.bio || 'No bio provided'}</p>
{/* Show user avatar or placeholder */}
<img src={user.avatar || '/default-avatar.png'} alt="Avatar" />
</div>
)
}π‘ Nullish Coalescing (??)
Modern JavaScript has the nullish coalescing operator (??) which only falls back for null or undefined, not for other falsy values like 0 or "":count || 'N/A' β "N/A" even if count is 0count ?? 'N/A' β 0 if count is 0
Method 5: Returning Null
Sometimes you want a component to render nothing at all. Return null to render nothing:
function AdminPanel({ user }) {
// Don't render anything if user is not admin
if (!user.isAdmin) {
return null
}
return (
<div className="admin-panel">
<h2>Admin Controls</h2>
<AdminButtons />
</div>
)
}
// Example with multiple conditions
function SpecialOffer({ isPremium, hasSeenOffer, offerExpired }) {
// Don't show if any of these conditions are true
if (isPremium || hasSeenOffer || offerExpired) {
return null
}
return (
<div className="special-offer">
<h3>Limited Time Offer!</h3>
<button>Upgrade Now</button>
</div>
)
}Method 6: Element Variables
You can store JSX elements in variables and conditionally assign them:
function StatusMessage({ status }) {
let message
let icon
if (status === 'success') {
icon = 'β
'
message = <p className="success">Operation successful!</p>
} else if (status === 'error') {
icon = 'β'
message = <p className="error">Something went wrong</p>
} else if (status === 'warning') {
icon = 'β οΈ'
message = <p className="warning">Please review your input</p>
} else {
icon = 'βΉοΈ'
message = <p className="info">Processing...</p>
}
return (
<div className="status-box">
<span className="icon">{icon}</span>
{message}
</div>
)
}This approach is great when:
- You have complex conditional logic
- You want to reuse the same element variable
- You need to build up the element conditionally
Method 7: Switch Statements
For multiple distinct conditions, switch statements can be cleaner than if/else chains:
function StatusIcon({ status }) {
switch (status) {
case 'loading':
return <Spinner />
case 'success':
return <CheckIcon />
case 'error':
return <ErrorIcon />
case 'warning':
return <WarningIcon />
default:
return <InfoIcon />
}
}
// Or using a switch with element variable
function Dashboard({ userRole }) {
let content
switch (userRole) {
case 'admin':
content = <AdminDashboard />
break
case 'moderator':
content = <ModeratorDashboard />
break
case 'user':
content = <UserDashboard />
break
default:
content = <GuestDashboard />
}
return (
<div className="dashboard">
{content}
</div>
)
}Method 8: Object Mapping
A clever technique is to use objects to map conditions to components:
function PaymentStatus({ status }) {
const statusComponents = {
pending: <PendingIcon />,
processing: <ProcessingSpinner />,
completed: <SuccessIcon />,
failed: <ErrorIcon />,
refunded: <RefundIcon />
}
// Return the component based on status, or null if not found
return statusComponents[status] || null
}
// More complex example with full components
function UserDashboard({ role }) {
const dashboards = {
admin: <AdminDashboard />,
moderator: <ModeratorDashboard />,
premium: <PremiumDashboard />,
user: <UserDashboard />,
guest: <GuestDashboard />
}
return dashboards[role] || dashboards.guest
}
// With functions for dynamic content
function Notification({ type, message }) {
const notificationConfig = {
success: {
icon: 'β
',
className: 'bg-green-100 text-green-800'
},
error: {
icon: 'β',
className: 'bg-red-100 text-red-800'
},
warning: {
icon: 'β οΈ',
className: 'bg-yellow-100 text-yellow-800'
},
info: {
icon: 'βΉοΈ',
className: 'bg-blue-100 text-blue-800'
}
}
const config = notificationConfig[type] || notificationConfig.info
return (
<div className={`notification ${config.className}`}>
<span>{config.icon}</span>
<p>{message}</p>
</div>
)
}π‘ When to Use Object Mapping
Object mapping is great when:
β’ You have many simple conditions (5+)
β’ Each condition maps to a component or configuration
β’ You want clean, readable code
β’ Conditions are based on string/number values
π‘ Tip: Edit the code above and click "Run" to see your changes
Best Practices for Conditional Rendering
Follow these guidelines to write clean conditional rendering:
1. Choose the Right Method
- If/Else: Multiple conditions, different components, early returns
- Ternary (? :): Two options, inline rendering, simple conditions
- Logical AND (&&): Show something or nothing, single condition
- Logical OR (||): Fallback values, default content
- Switch/Object: Many distinct options based on one value
2. Keep It Simple
// β Too complex
{isLoggedIn && user.isPremium && !user.isBanned && hasAccess &&
(userRole === 'admin' || userRole === 'moderator') && (
<ComplexComponent />
)}
// β
Extract to variable
const canAccessFeature = isLoggedIn &&
user.isPremium &&
!user.isBanned &&
hasAccess &&
(userRole === 'admin' || userRole === 'moderator')
{canAccessFeature && <ComplexComponent />}
// β
Or extract to function
const canUserAccess = (user) => {
if (!user.isLoggedIn || user.isBanned) return false
if (!user.isPremium || !user.hasAccess) return false
return user.role === 'admin' || user.role === 'moderator'
}
{canUserAccess(user) && <ComplexComponent />}3. Handle All Cases
// β Forgot the loading state
function UserProfile({ user, error }) {
if (error) return <Error />
return <Profile user={user} />
}
// β
Handle all possible states
function UserProfile({ user, isLoading, error }) {
if (isLoading) return <Spinner />
if (error) return <Error error={error} />
if (!user) return <NotFound />
return <Profile user={user} />
}4. Avoid Negative Conditions When Possible
// β Hard to read
{!isNotLoggedIn && !hasNoAccess && <Content />}
// β
Use positive conditions
{isLoggedIn && hasAccess && <Content />}5. Use Early Returns for Guards
// β
Clean and readable
function ProtectedContent({ user, data }) {
// Guard clauses at the top
if (!user) return <Login />
if (!user.hasAccess) return <Forbidden />
if (!data) return <Loading />
// Happy path
return (
<div>
<h1>Welcome, {user.name}!</h1>
<DataDisplay data={data} />
</div>
)
}Common Conditional Rendering Patterns
Loading, Error, Success Pattern
function DataFetcher({ url }) {
const [data, setData] = React.useState(null)
const [isLoading, setIsLoading] = React.useState(true)
const [error, setError] = React.useState(null)
// ... fetch logic ...
if (isLoading) {
return (
<div className="loading">
<Spinner />
<p>Loading data...</p>
</div>
)
}
if (error) {
return (
<div className="error">
<h2>Oops! Something went wrong</h2>
<p>{error.message}</p>
<button onClick={retry}>Try Again</button>
</div>
)
}
return (
<div className="success">
<DataDisplay data={data} />
</div>
)
}Empty State Pattern
function ItemList({ items, isLoading }) {
if (isLoading) {
return <LoadingSkeleton />
}
if (!items || items.length === 0) {
return (
<div className="empty-state">
<EmptyIcon />
<h3>No items yet</h3>
<p>Get started by adding your first item</p>
<button>Add Item</button>
</div>
)
}
return (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
)
}Permission-Based Rendering
function FeatureAccess({ user, requiredRole }) {
const hasPermission = user.roles.includes(requiredRole)
if (!hasPermission) {
return (
<div className="no-access">
<LockIcon />
<p>You don't have permission to access this feature</p>
<a href="/upgrade">Upgrade your account</a>
</div>
)
}
return <Feature />
}
// Usage:
<FeatureAccess user={currentUser} requiredRole="premium">
<PremiumFeature />
</FeatureAccess>Key Takeaways
- Conditional rendering shows/hides UI based on conditions
- Use if/else before return for multiple conditions or early returns
- Use ternary operator (
? :) for inline conditions with two options - Use logical AND (
&&) to show something or nothing - Watch out for falsy values like
0with&& - Return
nullto render nothing - Use switch statements or object mapping for many options
- Extract complex conditions to variables or functions
- Handle all states: loading, error, empty, success
- Use early returns for guard clauses
- Avoid deeply nested ternaries - they're hard to read
What's Next?
You now know how to show the right content at the right time using conditional rendering. But what about rendering multiple items from an array? What if you have a list of users, products, or messages to display?
That's where lists and keys come in! In the next lesson, you'll learn how to render arrays of data, why the key prop is crucial, and how to create dynamic lists that React can efficiently update. Get ready to bring your data to life! π