Conditional Rendering
Overview
Conditional rendering in React allows you to render different UI based on certain conditions, similar to how conditions work in JavaScript.
Conditional Rendering Patterns
1. If/Else with Early Return
function UserGreeting({ user }) {
if (!user) {
return <p>Please sign in.</p>;
}
if (user.role === 'admin') {
return <AdminDashboard user={user} />;
}
return <UserDashboard user={user} />;
}
2. Ternary Operator
function LoginButton({ isLoggedIn }) {
return <button>{isLoggedIn ? 'Logout' : 'Login'}</button>;
}
// With component rendering
function Dashboard({ isLoggedIn }) {
return <div>{isLoggedIn ? <UserPanel /> : <LoginForm />}</div>;
}
3. Logical AND (&&) Operator
function Notifications({ count }) {
return (
<div>
<h1>Notifications</h1>
{count > 0 && (
<p>
You have {count} new notification{count > 1 ? 's' : ''}!
</p>
)}
</div>
);
}
// Multiple conditions
function UserCard({ user, isAdmin }) {
return (
<div>
<h2>{user.name}</h2>
{user.isPremium && <Badge text='Premium' />}
{isAdmin && <AdminControls />}
{user.verified && <VerifiedIcon />}
</div>
);
}
4. Nullish Coalescing (??)
function UserProfile({ user }) {
return (
<div>
<h2>{user.name ?? 'Anonymous'}</h2>
<p>Points: {user.points ?? 0}</p>
<p>Bio: {user.bio ?? 'No bio available'}</p>
</div>
);
}
5. Switch Case (with IIFE)
function StatusIndicator({ status }) {
return (
<div>
{(() => {
switch (status) {
case 'loading':
return <Spinner />;
case 'success':
return <SuccessMessage />;
case 'error':
return <ErrorMessage />;
default:
return <IdleState />;
}
})()}
</div>
);
}
6. Object Mapping Pattern
function StatusDisplay({ status }) {
const statusComponents = {
loading: <Spinner />,
success: <SuccessIcon />,
error: <ErrorIcon />,
idle: <IdleIcon />,
};
return statusComponents[status] || <IdleIcon />;
}
// With configuration objects
function NotificationBadge({ type }) {
const config = {
info: { icon: '📢', color: 'blue', text: 'Info' },
warning: { icon: '⚠️', color: 'yellow', text: 'Warning' },
error: { icon: '❌', color: 'red', text: 'Error' },
success: { icon: '✅', color: 'green', text: 'Success' },
};
const { icon, color, text } = config[type] || config.info;
return (
<div style={{ backgroundColor: color }}>
<span>{icon}</span>
<span>{text}</span>
</div>
);
}
Complex Conditional Patterns
Multiple Conditions
function ProductCard({ product, user }) {
const canPurchase = user && !product.outOfStock && product.price > 0;
const showDiscount = product.discount && product.discount > 0;
const isPremiumProduct = product.isPremium;
const userIsPremium = user?.isPremium;
return (
<div className='product-card'>
<h3>{product.name}</h3>
<p>${product.price}</p>
{showDiscount && <span className='discount'>-{product.discount}%</span>}
{isPremiumProduct && !userIsPremium && (
<div className='premium-banner'>Premium members only</div>
)}
{product.outOfStock ? (
<span className='out-of-stock'>Out of Stock</span>
) : canPurchase ? (
<button>Add to Cart</button>
) : (
<button disabled>Unavailable</button>
)}
</div>
);
}
Nested Conditionals
function UserStatus({ user }) {
return (
<div>
{user ? (
user.isActive ? (
user.isPremium ? (
<PremiumActiveBadge />
) : (
<ActiveBadge />
)
) : (
<InactiveBadge />
)
) : (
<GuestBadge />
)}
</div>
);
}
// Better approach - flatten with early returns
function UserStatus({ user }) {
if (!user) return <GuestBadge />;
if (!user.isActive) return <InactiveBadge />;
if (user.isPremium) return <PremiumActiveBadge />;
return <ActiveBadge />;
}
Conditional Classes
function Button({ variant, isLoading, disabled, children }) {
const className = [
'btn',
variant === 'primary' && 'btn-primary',
variant === 'secondary' && 'btn-secondary',
isLoading && 'btn-loading',
disabled && 'btn-disabled',
]
.filter(Boolean)
.join(' ');
return (
<button className={className} disabled={disabled || isLoading}>
{isLoading ? 'Loading...' : children}
</button>
);
}
// Using template literals
function Card({ isHighlighted, hasError, className }) {
return (
<div
className={`
card
${isHighlighted ? 'card-highlighted' : ''}
${hasError ? 'card-error' : ''}
${className || ''}
`}
>
Content
</div>
);
}
// Using a utility library (classnames)
import classNames from 'classnames';
function Alert({ type, dismissible, className }) {
return (
<div
className={classNames(
'alert',
{
'alert-success': type === 'success',
'alert-error': type === 'error',
'alert-warning': type === 'warning',
'alert-dismissible': dismissible,
},
className
)}
>
Alert content
</div>
);
}
Conditional Styles
function ProgressBar({ progress, color }) {
return (
<div className='progress-bar'>
<div
className='progress-fill'
style={{
width: `${progress}%`,
backgroundColor:
color ||
(progress < 30 ? 'red' : progress < 70 ? 'yellow' : 'green'),
}}
/>
</div>
);
}
Loading States
Simple Loading
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.finally(() => setIsLoading(false));
}, [userId]);
if (isLoading) {
return <LoadingSpinner />;
}
return <UserCard user={user} />;
}
Loading with Error Handling
function DataDisplay({ url }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return <EmptyState />;
return <DataTable data={data} />;
}
Skeleton Loading
function UserList({ users, isLoading }) {
if (isLoading) {
return (
<div>
{Array(5)
.fill(0)
.map((_, i) => (
<SkeletonCard key={i} />
))}
</div>
);
}
return (
<div>
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
function SkeletonCard() {
return (
<div className='skeleton-card'>
<div className='skeleton skeleton-avatar' />
<div className='skeleton skeleton-text' />
<div className='skeleton skeleton-text short' />
</div>
);
}
Empty States
function TodoList({ todos, isLoading }) {
if (isLoading) {
return <Spinner />;
}
if (todos.length === 0) {
return (
<div className='empty-state'>
<h2>No todos yet!</h2>
<p>Create your first todo to get started</p>
<button>Add Todo</button>
</div>
);
}
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
Permission-based Rendering
function AdminPanel({ user, children }) {
if (!user) {
return <Redirect to='/login' />;
}
if (user.role !== 'admin') {
return <AccessDenied />;
}
return <div className='admin-panel'>{children}</div>;
}
// Using a permission component
function CanAccess({ user, permissions, fallback, children }) {
const hasPermission = permissions.every(p => user?.permissions?.includes(p));
return hasPermission ? children : fallback || null;
}
// Usage
<CanAccess
user={currentUser}
permissions={['admin', 'edit_users']}
fallback={<AccessDenied />}
>
<UserManagement />
</CanAccess>;
Feature Flags
function FeatureFlag({ feature, children, fallback }) {
const features = useFeatureFlags();
return features[feature] ? children : fallback || null;
}
// Usage
function App() {
return (
<div>
<FeatureFlag feature='new_dashboard'>
<NewDashboard />
</FeatureFlag>
<FeatureFlag feature='beta_feature' fallback={<ComingSoon />}>
<BetaFeature />
</FeatureFlag>
</div>
);
}
Best Practices
1. Keep Conditions Simple
// ❌ Complex nested conditions
{
user && user.isActive && user.isPremium && !user.isBanned && (
<PremiumContent />
);
}
// ✅ Extract to variables
const canAccessPremium = user?.isActive && user?.isPremium && !user?.isBanned;
{
canAccessPremium && <PremiumContent />;
}
// ✅ Or extract to function
const canUserAccessPremium = user => {
return user?.isActive && user?.isPremium && !user?.isBanned;
};
{
canUserAccessPremium(user) && <PremiumContent />;
}
2. Use Early Returns
// ❌ Nested ternaries
function Component({ data }) {
return (
<div>
{data ? (
data.items.length > 0 ? (
<ItemList items={data.items} />
) : (
<EmptyState />
)
) : (
<Loading />
)}
</div>
);
}
// ✅ Early returns
function Component({ data }) {
if (!data) return <Loading />;
if (data.items.length === 0) return <EmptyState />;
return <ItemList items={data.items} />;
}
3. Avoid Rendering Nothing with &&
// ❌ Can render 0 instead of nothing
{
items.length && <ItemList items={items} />;
}
// ✅ Explicit boolean check
{
items.length > 0 && <ItemList items={items} />;
}
// ✅ Or use ternary
{
items.length ? <ItemList items={items} /> : null;
}
4. Use Fragments
// ❌ Unnecessary wrapper div
function Component({ showExtra }) {
return (
<div>
<Header />
{showExtra && <Extra />}
</div>
);
}
// ✅ Use Fragment when needed
function Component({ showExtra }) {
return (
<>
<Header />
{showExtra && <Extra />}
</>
);
}
Common Pitfalls
// ❌ Rendering 0 instead of nothing
{
count && <div>Count: {count}</div>;
}
// ✅ Explicit check
{
count > 0 && <div>Count: {count}</div>;
}
// ❌ Boolean values rendering
{
isActive && 'Active';
} // Renders 'Active' string
// ✅ Proper boolean rendering
{
isActive ? 'Active' : 'Inactive';
}
// ❌ Mutating state in render
{
items.sort().map(item => <Item key={item.id} />);
}
// ✅ Don't mutate, create new array
{
[...items].sort().map(item => <Item key={item.id} />);
}
Practice Exercises
- Create a multi-step form with conditional rendering for each step
- Build a user dashboard that shows different content based on user role
- Implement a product filter with conditional rendering for filters
- Create a notification system with different rendering for notification types
Next Steps
- Learn about Lists and Keys
- Explore exercises in the exercises folder