Why Conditional Rendering?
Conditional rendering is like a choose-your-own-adventure book - different paths lead to different outcomes based on decisions. In React, we use conditions to determine what gets displayed on the screen.
Basic Conditional Rendering Patterns
1. If/Else Statements
// Using if/else before return
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please sign up.</h1>;
}
}
// Using if/else with variables
function UserDashboard({ user }) {
let content;
if (user) {
content = (
<div>
<h2>Hello, {user.name}!</h2>
<p>Email: {user.email}</p>
</div>
);
} else {
content = <p>Please log in to see your dashboard.</p>;
}
return <div className="dashboard">{content}</div>;
}
2. Ternary Operator
// Simple ternary for inline conditions
function StatusBadge({ isOnline }) {
return (
<span className={`badge ${isOnline ? 'online' : 'offline'}`}>
{isOnline ? 'Online' : 'Offline'}
</span>
);
}
// Nested ternary (use sparingly)
function UserStatus({ user }) {
return (
<div>
{user ? (
user.isAdmin ? (
<AdminPanel user={user} />
) : (
<UserPanel user={user} />
)
) : (
<GuestPanel />
)}
</div>
);
}
// Better approach for complex conditions
function UserStatusImproved({ user }) {
if (!user) return <GuestPanel />;
if (user.isAdmin) return <AdminPanel user={user} />;
return <UserPanel user={user} />;
}
3. Logical AND (&&) Operator
// Show component only when condition is true
function Notification({ message, show }) {
return (
<div>
{show && (
<div className="notification">
{message}
</div>
)}
</div>
);
}
// Multiple conditions
function UserProfile({ user, isLoading, error }) {
return (
<div>
{isLoading && <LoadingSpinner />}
{error && <ErrorMessage error={error} />}
{user && !isLoading && !error && (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
)}
</div>
);
}
// ⚠️ Watch out for falsy values!
function BadExample({ count }) {
return (
<div>
{/* This will render "0" when count is 0 */}
{count && <p>You have {count} messages</p>}
{/* Better approach */}
{count > 0 && <p>You have {count} messages</p>}
</div>
);
}
4. Logical OR (||) Operator for Defaults
// Providing default content
function WelcomeMessage({ username }) {
return (
<h1>Welcome, {username || 'Guest'}!</h1>
);
}
// Using nullish coalescing (??) for better control
function UserAvatar({ avatarUrl, defaultUrl }) {
return (
<img
src={avatarUrl ?? defaultUrl}
alt="User avatar"
/>
);
}
Advanced Conditional Patterns
1. IIFE (Immediately Invoked Function Expression)
function ComplexConditions({ user, permissions }) {
return (
<div>
{(() => {
if (!user) return <LoginPrompt />;
if (!permissions.canView) return <AccessDenied />;
if (permissions.isAdmin) return <AdminDashboard />;
if (permissions.isModerator) return <ModeratorDashboard />;
return <UserDashboard />;
})()}
</div>
);
}
2. Switch Statements
// Using switch for multiple conditions
function NotificationIcon({ type }) {
switch (type) {
case 'success':
return <SuccessIcon />;
case 'warning':
return <WarningIcon />;
case 'error':
return <ErrorIcon />;
case 'info':
return <InfoIcon />;
default:
return <DefaultIcon />;
}
}
// Switch with render function
function renderContent(status) {
switch (status) {
case 'loading':
return <LoadingSpinner />;
case 'error':
return <ErrorMessage />;
case 'success':
return <SuccessContent />;
default:
return null;
}
}
function DataDisplay({ status }) {
return (
<div className="data-display">
{renderContent(status)}
</div>
);
}
3. Object Mapping Pattern
// Map conditions to components
function StatusDisplay({ status }) {
const statusComponents = {
pending: <PendingStatus />,
approved: <ApprovedStatus />,
rejected: <RejectedStatus />,
cancelled: <CancelledStatus />
};
return statusComponents[status] || <UnknownStatus />;
}
// More complex mapping with props
function DynamicComponent({ type, props }) {
const components = {
button: Button,
input: Input,
select: Select,
textarea: TextArea
};
const Component = components[type] || DefaultComponent;
return <Component {...props} />;
}
Conditional Rendering with Hooks
1. useState for UI State
function ToggleContent() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? 'Hide' : 'Show'} Content
</button>
{isVisible && (
<div className="content">
<p>This content can be toggled</parameter>
</div>
)}
</div>
);
}
2. useEffect for Conditional Side Effects
function DataFetcher({ shouldFetch, query }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!shouldFetch || !query) return;
setLoading(true);
fetchData(query)
.then(setData)
.finally(() => setLoading(false));
}, [shouldFetch, query]);
if (!shouldFetch) {
return <p>Data fetching is disabled</p>;
}
return loading ? <LoadingSpinner /> : <DataDisplay data={data} />;
}
Common Conditional Rendering Patterns
1. Loading States
function DataComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) {
return (
<div className="loading-container">
<LoadingSpinner />
<p>Loading data...</p>
</div>
);
}
if (error) {
return (
<div className="error-container">
<ErrorIcon />
<p>Error: {error.message}</p>
<button onClick={() => window.location.reload()}>
Retry
</button>
</div>
);
}
if (!data || data.length === 0) {
return (
<div className="empty-state">
<EmptyIcon />
<p>No data available</p>
</div>
);
}
return (
<div className="data-container">
{data.map(item => (
<DataItem key={item.id} item={item} />
))}
</div>
);
}
2. Authentication-Based Rendering
// Auth wrapper component
function PrivateRoute({ children, requiredRole }) {
const { user, loading } = useAuth();
if (loading) {
return <LoadingSpinner />;
}
if (!user) {
return <Navigate to="/login" />;
}
if (requiredRole && user.role !== requiredRole) {
return <AccessDenied />;
}
return children;
}
// Usage
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/dashboard"
element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
}
/>
<Route
path="/admin"
element={
<PrivateRoute requiredRole="admin">
<AdminPanel />
</PrivateRoute>
}
/>
</Routes>
);
}
3. Feature Flags
// Feature flag context
const FeatureFlagContext = createContext({});
function FeatureFlagProvider({ children }) {
const [features, setFeatures] = useState({
newDashboard: false,
betaFeatures: false,
advancedAnalytics: true
});
return (
<FeatureFlagContext.Provider value={features}>
{children}
</FeatureFlagContext.Provider>
);
}
// Feature flag hook
function useFeature(featureName) {
const features = useContext(FeatureFlagContext);
return features[featureName] || false;
}
// Using feature flags
function Dashboard() {
const hasNewDashboard = useFeature('newDashboard');
return hasNewDashboard ? <NewDashboard /> : <LegacyDashboard />;
}
4. Responsive Rendering
// Media query hook
function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [matches, query]);
return matches;
}
// Responsive component
function ResponsiveLayout() {
const isMobile = useMediaQuery('(max-width: 768px)');
const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
if (isMobile) {
return <MobileLayout />;
}
if (isTablet) {
return <TabletLayout />;
}
return <DesktopLayout />;
}
Performance Considerations
1. Conditional Component Mounting
// Component is unmounted when condition is false
function ConditionalMount({ show, children }) {
return show ? children : null;
}
// Component is hidden but still mounted
function ConditionalDisplay({ show, children }) {
return (
<div style={{ display: show ? 'block' : 'none' }}>
{children}
</div>
);
}
// When to use each:
// - ConditionalMount: For expensive components
// - ConditionalDisplay: For frequently toggled content
2. Memoizing Conditional Components
const ExpensiveComponent = React.memo(({ data }) => {
console.log('Expensive component rendered');
// Complex rendering logic
return <div>{/* ... */}</div>;
});
function OptimizedConditional({ showExpensive, data }) {
const memoizedComponent = useMemo(() => {
return showExpensive ? <ExpensiveComponent data={data} /> : null;
}, [showExpensive, data]);
return (
<div>
{memoizedComponent}
</div>
);
}
Best Practices
Anti-patterns to Avoid
// ❌ Deeply nested ternaries
function BadNesting({ user, permissions, settings }) {
return (
<div>
{user ? (
permissions ? (
settings.enabled ? (
<Dashboard />
) : (
<DisabledMessage />
)
) : (
<NoPermission />
)
) : (
<LoginPrompt />
)}
</div>
);
}
// ✅ Better approach
function GoodStructure({ user, permissions, settings }) {
if (!user) return <LoginPrompt />;
if (!permissions) return <NoPermission />;
if (!settings.enabled) return <DisabledMessage />;
return <Dashboard />;
}
// ❌ Rendering numbers directly
function BadNumberRendering({ count }) {
// This renders "0" when count is 0
return <div>{count && `You have ${count} items`}</div>;
}
// ✅ Better approach
function GoodNumberRendering({ count }) {
return <div>{count > 0 && `You have ${count} items`}</div>;
}