Stop Writing the Same Conditional Logic Over and Over — Build Reusable Components Instead
A practical guide to creating declarative, reusable conditional components that will make your React code cleaner and more maintainable
You know that feeling when you’re writing the same {isLoading ? <Spinner /> : ...} pattern for the hundredth time?
Yeah, me too.
After reviewing hundreds of React codebases, I’ve noticed something: we’re all solving the same problems differently. One component uses ternary operators, another uses if/else statements, and yet another has some wild nested conditionals that make you question your life choices.
What if I told you there’s a better way?
Let me show you how to create reusable conditional rendering components that will make your code look like this:
<Show when={isLoggedIn} fallback={<Login />}>
<Dashboard />
</Show>Instead of this:
{isLoading ? (
<Spinner />
) : error ? (
<Error message={error} />
) : !data ? (
<EmptyState />
) : (
<DataDisplay data={data} />
)}Cleaner, right? Let’s dive in.
The Problem We’re Solving
Here’s a typical React component I found in a production codebase (names changed to protect the innocent):
function UserDashboard() {
const { user, isLoading, error } = useUser();
if (isLoading) {
return <div className="spinner"><Spinner /></div>;
}
if (error) {
return <div className="error"><ErrorMessage error={error} /></div>;
}
if (!user) {
return <div className="empty"><Login /></div>;
}
return (
<div>
<h1>Welcome, {user.name}</h1>
{user.isPremium ? (
<PremiumDashboard />
) : (
<FreeDashboard />
)}
{user.notifications.length > 0 && (
<NotificationBadge count={user.notifications.length} />
)}
</div>
);
}Nothing terrible here, but imagine this pattern repeated across 50+ components. The inconsistency compounds.
Let’s fix this.
1. The <Show> Component
This is the simplest and most useful component you’ll build. It handles the most common case: show something when a condition is true.
function Show({ when, fallback = null, children }) {
return when ? children : fallback;
}That’s it. Three lines of code that will save you hundreds.
How to use it:
// Simple visibility
<Show when={isLoggedIn}>
<Dashboard />
</Show>
// With fallback
<Show when={user} fallback={<Login />}>
<Profile user={user} />
</Show>
// No more && gotchas
<Show when={items.length > 0}>
<ItemList items={items} />
</Show>“Pro tip: The
whenprop accepts any truthy/falsy value, so you can pass objects, arrays, or booleans directly.”
Why this is better:
✅ Declarative — reads like plain English
✅ Consistent — same pattern everywhere
✅ No more 0 rendering on screen — explicit fallback handling
✅ Easy to test — simple, predictable behavior
2. The <Visible> Component
For CSS-Based Conditional Rendering
function Visible({ when, keepMounted = false, children }) {
if (!keepMounted && !when) {
return null;
}
return (
<div style={{ display: when ? 'block' : 'none' }}>
{children}
</div>
);
}
// Usage
<Visible when={showModal}>
<Modal />
</Visible>
// Keep in DOM but hide (useful for animations)
<Visible when={isExpanded} keepMounted>
<ExpandedContent />
</Visible>3. The <If> / <Else> Component
More Readable Syntax
function If({ condition, children }) {
return condition ? children : null;
}
function Else({ children }) {
return children;
}
// Helper to make If/Else work together
function Conditional({ condition, children }) {
const childrenArray = React.Children.toArray(children);
if (condition) {
return childrenArray.find(child => child.type === If) || null;
} else {
return childrenArray.find(child => child.type === Else) || null;
}
}
// Usage
<Conditional condition={isLoggedIn}>
<If>
<Dashboard />
</If>
<Else>
<Login />
</Else>
</Conditional>Better Alternative — Simple Version:
function When({ condition, children }) {
return condition ? children : null;
}
function Otherwise({ children }) {
return children;
}
function Choose({ children }) {
let whenChild = null;
let otherwiseChild = null;
React.Children.forEach(children, child => {
if (child.type === When && !whenChild && child.props.condition) {
whenChild = child;
} else if (child.type === Otherwise) {
otherwiseChild = child;
}
});
return whenChild || otherwiseChild || null;
}
// Usage
<Choose>
<When condition={user.role === 'admin'}>
<AdminPanel />
</When>
<When condition={user.role === 'moderator'}>
<ModPanel />
</When>
<Otherwise>
<UserPanel />
</Otherwise>
</Choose>4. The <Switch> Component — For Multiple Conditions
Remember writing code like this?
{status === 'idle' && <Idle />}
{status === 'loading' && <Loading />}
{status === 'success' && <Success />}
{status === 'error' && <Error />}Or even worse:
{status === 'idle' ? <Idle /> :
status === 'loading' ? <Loading /> :
status === 'success' ? <Success /> :
status === 'error' ? <Error /> : null}There’s a better way:
function Switch({ value, children }) {
let matchedChild = null;
let defaultChild = null;
React.Children.forEach(children, child => {
if (!matchedChild && React.isValidElement(child)) {
if (child.props.value === value) {
matchedChild = child;
} else if (child.props.default) {
defaultChild = child;
}
}
});
return matchedChild || defaultChild || null;
}
function Case({ children }) {
return children;
}Usage:
<Switch value={userRole}>
<Case value="admin">
<AdminDashboard />
</Case>
<Case value="moderator">
<ModDashboard />
</Case>
<Case value="user">
<UserDashboard />
</Case>
<Case default>
<GuestView />
</Case>
</Switch>5. The <Authenticated> Component — Guard Your Routes
Authentication checks are everywhere. Let’s make them consistent:
function Authenticated({
fallback = <LoginPage />,
loading = <Spinner />,
children
}) {
const { user, isLoading } = useAuth();
if (isLoading) return loading;
if (!user) return fallback;
// Support render props to access user data
return typeof children === 'function' ? children(user) : children;
}How you’ll use it:
function App() {
return (
<Authenticated fallback={<Login />} loading={<AppLoading />}>
{(user) => (
<>
<Header user={user} />
<Dashboard userId={user.id} />
</>
)}
</Authenticated>
);
}No more useAuth() in every component. No more forgetting to check isLoading. Just wrap and go.
6. The <Guard> Component — Permission-Based Rendering
Need to hide features based on user permissions? This one’s for you:
function Guard({
requires,
fallback = <AccessDenied />,
loading = <Spinner />,
children
}) {
const { user, isLoading } = useAuth();
if (isLoading) return loading;
if (!user) return fallback;
// Single permission
if (typeof requires === 'string') {
return user.permissions?.includes(requires) ? children : fallback;
}
// Multiple permissions (user needs ANY of them)
if (Array.isArray(requires)) {
const hasPermission = requires.some(perm =>
user.permissions?.includes(perm)
);
return hasPermission ? children : fallback;
}
return children;
}Usage
<Guard requires="admin">
<DeleteButton />
</Guard>
<Guard requires={['editor', 'admin']} fallback={<UpgradePrompt />}>
<EditArticleButton />
</Guard>7. The <DataState> Component — Because We All Fetch Data
If you’re fetching data (and who isn’t?), you need this component in your life.
Get om prakash’s stories in your inbox
Join Medium for free to get updates from this writer.
Here’s the pattern we’re always writing:
if (isLoading) return <Spinner />;
if (error) return <Error />;
if (!data) return <Empty />;
return <Display data={data} />;Let’s turn that into a component:
function DataState({
data,
isLoading,
error,
loadingComponent = <DefaultSpinner />,
errorComponent,
emptyComponent = <EmptyState />,
children
}) {
if (isLoading) return loadingComponent;
if (error) {
return errorComponent || <ErrorDisplay error={error} />;
}
if (!data || (Array.isArray(data) && data.length === 0)) {
return emptyComponent;
}
// Support render props for accessing data
return typeof children === 'function' ? children(data) : children;
}Usage:
function BlogPosts() {
const { posts, isLoading, error } = usePosts();
return (
<DataState
data={posts}
isLoading={isLoading}
error={error}
loadingComponent={<PostsSkeleton />}
errorComponent={<ErrorMessage error={error} />}
emptyComponent={<NoPosts />}
>
{(posts) => <PostList posts={posts} />}
</DataState>
);
}8. The <ConditionalWrapper> Component — The Secret Weapon
Sometimes you need to conditionally wrap children with a component (like a link or tooltip):
function ConditionalWrapper({ condition, wrapper, children }) {
return condition ? wrapper(children) : children;
}Usage:
// Make images clickable only when there's a link
<ConditionalWrapper
condition={hasLink}
wrapper={(children) => <a href={url}>{children}</a>}
>
<img src={imageSrc} alt={alt} />
</ConditionalWrapper>
// Add tooltip only for disabled buttons
<ConditionalWrapper
condition={isDisabled}
wrapper={(children) => (
<Tooltip content="You need premium to access this">
{children}
</Tooltip>
)}
>
<Button disabled={isDisabled}>Premium Feature</Button>
</ConditionalWrapper>No more duplicating the child component in both branches!
Your Starter Kit — Copy, Paste, Ship
Here’s a complete Conditional.jsx file you can drop into any project:
TypeScript? I Got You.
Here’s the fully typed version:
Best Practices I’ve Learned the Hard Way
✅ DO:
// Keep components simple and focused
<Show when={isLoggedIn}>
<Dashboard />
</Show>
// Use render props for data access
<DataState data={users}>
{(users) => <UserList users={users} />}
</DataState>
// Provide meaningful fallbacks
<Show when={user} fallback={<LoginPrompt />}>
<UserProfile />
</Show>❌ DON'T:
// Don't over-complicate
<Show when={isLoggedIn}>
<Show when={isPremium}>
<Show when={hasAccess}>
<Content /> {/* Too nested! */}
</Show>
</Show>
</Show>
// Don't use when regular conditionals are clearer
<Show when={true}> {/* Just render it! */}
<Component />
</Show>Conclusion
Creating reusable conditional rendering components:
✅ Makes code more declarative and readable
✅ Reduces duplication across your app
✅ Provides consistent patterns for your team
✅ Makes refactoring easier
✅ Improves testability
Start with these three:
<Show>- for simple conditionals<DataState>- for API data<Switch>/<Case>- for multiple conditions








