Sitemap

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

Press enter or click to view image in full size

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 when prop 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:

  1. <Show> - for simple conditionals
  2. <DataState> - for API data
  3. <Switch>/<Case> - for multiple conditions

Thanks for reading!

If you found this useful, please clap 👏, share this, and leave a comment below.

You can also follow me on GitHub and LinkedIn for more updates.

Happy coding! 🚀

Written by om prakash

Full-stack dev | Problem-solver | Tech enthusiast | Passionate about building user-focused web applications and exploring new tools.