window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster already exists') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Component Visibility in React with Conditional Rendering and Guards - The Codest
The Codest
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Industries
    • Fintech & Banking
    • E-commerce
    • Adtech
    • Healthtech
    • Manufacturing
    • Logistics
    • Automotive
    • IOT
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
Back arrow GO BACK
2023-05-19
Software Development

Component Visibility in React with Conditional Rendering and Guards

Bartlomiej Kuczynski

Learn how to simplify and improve component visibility in React using conditional rendering and guard components.

Today I would like to discuss how to control component visibility in React. But before we start there is small
disclaimer:

Presented solution is not very secure in meaning of protecting your application against „hackers” (whoever there are).
Remember that you need to protect your endpoints and to use good practices in application design. This solution only
makes your work a little easier.

Task, problem solution, or what we would like to achieve

One of the most common functionalities is to show component only for a bunch of users that have some specific rights,
roles or privileges. Common solution is to add some ifs to code, manually check conditions and show or not elements.

Let take a look to SimpleAppHeader component that contains few navigation elements.

<!-- wp:paragraph -->
<p><code>typescript jsx<br>
export const SimpleAppHeader: FC = () =&gt; {<br>
    return (<br>
        &lt;header&gt;<br>
            &lt;nav&gt;<br>
                &lt;ul&gt;<br>
                    {<br>
                        currentUser.role === "ADMIN" &amp;&amp;<br>
                        &lt;li&gt;Admin only component&lt;/li&gt;<br>
                    }<br>
                    {<br>
                        currentUser.role === "USER" &amp;&amp;<br>
                        &lt;li&gt;User only component&lt;/li&gt;<br>
                    }<br>
                    {<br>
                        (currentUser.role === "ADMIN" || currentUser.role === "USER") &amp;&amp;<br>
                        &lt;li&gt;Admin and User only component&lt;/li&gt;<br>
                    }<br>
                    &lt;li&gt;General usage element&lt;/li&gt;<br>
                &lt;/ul&gt;<br>
            &lt;/nav&gt;<br>
        &lt;/header&gt;<br>
    )<br>
}<br>
</code></p>
<!-- /wp:paragraph -->

Looks „not bad”. Probably you could encapsulate checks into functions to reduce complexity. currentUser is some object
that has role property, but it could be anything that we would like to use as subject of our control mechanism.

This code has some problems. If project grows we probably use that constructions in many places. So we need to copy,
somehow, conditions. This code are hard to maintain and changing in a future. Specially when access rules change during
time e.g. we need to change currentUser into something else. It is very hard to test. You need to write many tests
just to verify if your condition is ok.

Simplification

Time to simplify this code a little. We could extract some methods and make code shorter and less complex:

const isAdmin = (role: string): boolean => role === "ADMIN";
const isUser = (role: string): boolean => role === "USER";
const isAdminOrUser = (role: string): boolean => isAdmin(role) || isUser(role);

export const SimplifiedAppHeader: FC = () => {
return (

{

isAdmin(currentUser.role) &&
Admin only component

}
{
isUser(currentUser.role) &&

User only component

}
{
(isAdminOrUser(currentUser.role)) &&

Admin and User only component

}

General usage element

)
}
```

Looks promising. We reduce noise od repeat lines. Code is more readable and easier to maintain. But take a look to
function isAdminOrUser. We have only two roles. If we introduce third role we need to create another set of functions
that combines roles. Time to another version.

Filtering

Let introduce function hasRole that will be replacement for our isX functions.

const hasRole = (role: string, requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => role === s);
return found !== undefined;
}

export const FilteringAppHeader: FC = () => {
return (

{

hasRole(currentUser.role, ["ADMIN"]) &&
Admin only component

}
{
hasRole(currentUser.role, ["USER"]) &&

User only component

}
{
hasRole(currentUser.role, ["ADMIN", "USER"]) &&

Admin and User only component

}

General usage element

)
}
```

Now looks good. We still have conditions in html part of code, but now we can test hasRole function and trust that it
will be used with correct set of parameters. Adding or changing roles is now easier. We use array that contains all
roles that we need in place.

However, this solution is bind to currentUser object. Assumption is that object is somehow global or easy to access in
any place in application. So we can try to encapsulate this. Of course, we can move it to hasRole function:

 const hasRole = (requiredRole: string[]): boolean => {
     let found: string | undefined = requiredRole.find(s => currentUser.role === s);
     return found !== undefined;
 }

But that gives us almost nothing.

The Guard Component

Time to create component that encapsulate whole logic. I named it Guard and this is how I want to use it.

 export const GuardedAppHeader: FC = () => {
     return (
         <header>
             <nav>
                 <ul>
                     <Guard requiredRoles={["ADMIN"]}>
                         <li>Admin only component</li>
                     </Guard>
                     <Guard requiredRoles={["USER"]}>
                         <li>User only component</li>
                     </Guard>
                     <Guard requiredRoles={["USER", "ADMIN"]}>
                         <li>Admin and User component</li>
                     </Guard>
                     <li>General usage element</li>
                 </ul>
             </nav>
         </header>
     );
 }

Component need two properties. First children responsible for content that is guarded. Second requiredRoles that
handle array of roles that gives us access.

We need representation of this struct. It is very simple. We extends type React.PropsWithChildren that has children
property. Of course you can manually add that property to your type and omit extension.

interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
}

Component itself is simple too. We will reuse hasRole function here.

export const Guard = (props: IGuardProps) => {
const hasRole = (requiredRole: string[]): boolean => {
let found: string | undefined = requiredRole.find(s => currentUser.role === s);
return found !== undefined;
}

if (hasRole(props.requiredRoles)) {
    return (
        <>
            {props.children}
        </>
    );
} else {
    return <></>;
}
}
“`

And I could say stop here, but it will be too easy. Now we have component, and we can use it to the extreme 😀

`GuardService`

First step will be externalization of checks. currentUser is hardcoded value. I would like to encapsulate this value
into some service, that will „know” how to verify roles. Technically that means we move hasRole function to another
class.

I create simple interface IGuardService that has only one property – hasRole.

export interface IGuardService {
checkRole: (roles: string[]) => boolean;
}

Now simple implementation could look like this

class SimpleGuard implements IGuardService {
checkRole(roles: string[]): boolean {
let found: string | undefined = roles.find(e => e === currentUser.role);
return found !== undefined;
}
}

To use it we need to change IGuardProperties and use case.

export interface IGuardProps extends React.PropsWithChildren {
requiredRoles: string[];
guardService: IGuardService;
}

// …

const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (

Admin only component
 

User only component

Admin and User component
 
 

General usage element

 

);
}
```

Now component looks like:

 export const Guard = (props: IGuardProps) => {
     if (props.guardService.checkRole(props.requiredRoles)) {
         return (
             <>
                 {props.children}
             </>
         );
     } else {
         return <></>;
     }
 }
 

Much better. GuardService separate us from logic that check roles. We can change it without consequences for our
component. Common use case is to use mock in tests and some „real” implementation in production code.

Forbidden element

Next improvement will be handling custom Forbidden element. Current solution renders empty element. First we need to
change IGuardProps adding function that will be rendered that element.

 export interface IGuardProps extends React.PropsWithChildren {
     requiredRoles: string[];
     guardService: IGuardService;
     forbidden?: () => React.ReactNode;
 }

This is optional property, name ends with question mark character. So it could be function or undefined. We need to
handle it in Guard component.

export const Guard = (props: IGuardProps) => {
if (props.guardService.checkRole(props.requiredRoles)) {
return (
<>
{props.children}
</>
);
} else if (props.forbidden === undefined) {
return <></>;
} else {
return (<>
{props.forbidden()}
</>
);
}
}

// …

export const AppHeader: FC = () => {
const guardService = new SimpleGuard();
return (

Admin only component
 

User only component

Forbidden – only moderator can see this
}>
 

Moderator component

 

Admin and User component
 
 

General usage element

 

);
}
```

Flexibility of types

Time to last big change. Current version component supports only roles as string. We could have multiple different
types of property that we would like to check. Numbers or custom types is nothing special. I will add generics support.

First step is changes in IGuardService interface. Implementations will be depended on type of tested value. It could
be anything, so interface should handle it.

 export interface IGuardService<ROLE> {
     checkRole: (roles: ROLE[]) => boolean;
 }

Now it takes array of ROLE generic type. Our simple implementation will change a little.

 class SimpleGuard implements IGuardService<string> {
     checkRole(roles: string[]): boolean {
         let found: string | undefined = roles.find(e => e === currentUser.role);
         return found !== undefined;
     }
 }

We need to add type parameter, but we could prepare implementation that supports IRole interface.

interface IRole {
name: string;
}

//…
class RoleGuardService implements IGuardService {
checkRole(roles: IRole[]): boolean {
let found: IRole | undefined = roles.find(e => e === userWithRole.role);
return found !== undefined;
}
}
```

Second step is to propagate this change to IGuardProps.

 interface IGuardProps<ROLE> extends React.PropsWithChildren {
     requiredRoles: ROLE[];
     guardService: IGuardService<ROLE>;
     forbidden?: () => React.ReactNode;
 }

And respectively to Guard component.

export const Guard = <S, >(props: IGuardProps<S>) => {
     if (props.guardService.checkRole(props.requiredRoles)) {
         return (
             <>
                 {props.children}
             </>
         );
     } else if (props.forbidden === undefined) {
         return <></>;
     } else {
         return (<>
                 {props.forbidden()}
             </>
         );
     }
 }

And our use cases

export const AppHeader: FC = () => {
     const guardService = new SimpleGuard();
     const roleService = new RoleGuardService();
     return (
         <header>
             <nav>
                 <ul>
                     <Guard<IRole> requiredRoles={[{name: "ADMIN"}]} guardService={roleService}>
                         <li>Admin only component</li>
                     </Guard>
                     <Guard<string> requiredRoles={["USER"]} guardService={guardService}>
                         <li>User only component</li>
                     </Guard>
                     <Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
                                    forbidden={() => <div>Forbidden – only moderator can see this</div>}>
                         <li>Moderator component</li>
                     </Guard>
                     <Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
                         <li>Admin and User component</li>
                     </Guard>
                     <li>General usage element</li>
                 </ul>
             </nav>
         </header>
     );
 }

In this example I use both implementation of IGuardservice just for illustrative purposes. In real use cases you
probably use only one.

Specialized components and nesting

The Guard could be nested. Just remember that access will be resolved in order from most external instance.

export const NestAppHeader: FC = () => {
     const guardService = new SimpleGuard();
     return (
         <header>
             <nav>
                 <ul>
                     <Guard<string> requiredRoles={["USER", "ADMIN"]} guardService={guardService}>
                         <Guard<string> requiredRoles={["ADMIN"]} guardService={guardService}>
                             <li>Admin only component</li>
                         </Guard>
                         <Guard<string> requiredRoles={["USER"]} guardService={guardService}>
                             <li>User only component</li>
                         </Guard>
                         <li>Admin and User component</li>
                         <Guard<string> requiredRoles={["MODERATOR"]} guardService={guardService}
                                        forbidden={() => <div>Forbidden – only moderator can see this</div>}>
                             <li>Moderator component</li>
                         </Guard>
                     </Guard>
                     <li>General usage element</li>
                 </ul>
             </nav>
         </header>
     );
 }

In above example Moderator component could never appear, because user can handle only one role. First Guard limits
roles to ADMIN and USER, so MODERATOR will never pass first check.

We can build specialized components that hide some properties.

export const AdminGuard = (props: Omit<IGuardProps, "requiredRoles">) => {
return <Guard requiredRoles={["ADMIN"]} guardService={props.guardService} forbidden={props.forbidden}>
{props.children}

}

//…
export const SpecializedAppHeader: FC = () => {
const guardService = new SimpleGuard();
return (

Admin only component
 

<Guard requiredRoles={["USER"]} guardService={guardService}>
User only component

<Guard requiredRoles={["MODERATOR"]} guardService={guardService}
forbidden={() =>
Forbidden – only moderator can see this
}>

Moderator component

<Guard requiredRoles={["USER", "ADMIN"]} guardService={guardService}>

Admin and User component

 

General usage element

);
}
```

In this case AdminGuard predefine ADMIN role. In consequences, we need to explicit define type of ROLE type
parameter.

Sum up

In this article I show you how to create and use Guard component in React. We start from complex code that is hard to
read and maintain. We evolve it into more developer friendly state and introduce custom functional component. Next we
extend component adding extra functionalities, refactor extracting service and finally add generics types.

Finally, we have have component that could be nested is easy to test and maintain.

Related articles

Software Development

Pros and Cons of React

Why is it worth to use React? What advantages this JavaScript library has? To find out the answers dive into this article and discover the real benefits of using React.

The Codest
Cezary Goralski Software Engineer

Subscribe to our knowledge base and stay up to date on the expertise from the IT sector.

    About us

    The Codest – International software development company with tech hubs in Poland.

    United Kingdom - Headquarters

    • Office 303B, 182-184 High Street North E6 2JA
      London, England

    Poland - Local Tech Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warsaw, Poland

      The Codest

    • Home
    • About us
    • Services
    • Case Studies
    • Know How
    • Careers
    • Dictionary

      Services

    • It Advisory
    • Software Development
    • Backend Development
    • Frontend Development
    • Staff Augmentation
    • Backend Developers
    • Cloud Engineers
    • Data Engineers
    • Other
    • QA Engineers

      Resources

    • Facts and Myths about Cooperating with External Software Development Partner
    • From the USA to Europe: Why do American startups decide to relocate to Europe
    • Tech Offshore Development Hubs Comparison: Tech Offshore Europe (Poland), ASEAN (Philippines), Eurasia (Turkey)
    • What are the top CTOs and CIOs Challenges?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2025 by The Codest. All rights reserved.

    en_USEnglish
    de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek en_USEnglish