Skip to main content

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

  1. Create a multi-step form with conditional rendering for each step
  2. Build a user dashboard that shows different content based on user role
  3. Implement a product filter with conditional rendering for filters
  4. Create a notification system with different rendering for notification types

Next Steps

  • Learn about Lists and Keys
  • Explore exercises in the exercises folder