Permission based components with React's HOC

gdstp

Gabriel Teles

Posted on July 25, 2024

Permission based components with React's HOC

For those who like to jump straight to code. Live preview

Typically when it comes to permission components we all think about having an extra provider around the application, but personally I don't like having 3 to 4 extra lines every time we need a permission based component.

First things first:
This is a walkthrough process on how to accomplish permissions level components, it's important that you have basic knowledge in React as well as Typescript. For sake of simplicity we will not handle anything related to server, we will be using static data.
We will be using Next@14 but you should be able to accomplish the same result in any other react based framework.

React HOC

This is one of my favorites techniques to use in React, although there aren't many cases where it can be explored.
The idea behind the HOC is quite similar to the Provider. We will have a wrapper to our component, and this wrapper will be validating if the user has permission.

Before we dive into the code, this is an example on how our component would look like at the end

<Button permissions={[PERMISSIONS.ADD_NOITIFICATION]} />
Enter fullscreen mode Exit fullscreen mode

Creating the HOC withPermission

First thing we need is to get the component that should be wrapped with the permissions

export function withPermission<T>(Component: React.FC<T>) {
   const ComponentWithPermission: React.FC<T & React.Attributes> = 
      (props) => {
         return <Component {...props} />;
   };

   return ComponentWithPermission;
}
Enter fullscreen mode Exit fullscreen mode

Our HOC will receive a Component as an arg. We will capture all the component props and use it on our permission component hence the generic.

Inside the withPermission HOC we need to define a new component in order to inject the permission prop to it.

We will repeat the generic T as it will inherit all the props from the original component and this time we need to include a React.Attributes prop. Otherwise the component won't render.

In the end we return the newly created ComponentWithPermission.

What is exactly this React.Attributes prop?
Have you ever noticed that every react component receives a key prop? It's used to help React identify which items have changed, are added or removed.

The base structure for the HOC is done now it's time to inject the permission prop to it.

To make sure we don't have any typos we will be using an ENUM.

export enum Permissions {
  VIEW_INPUT = "VIEW_INPUT",
  VIEW_BUTTON = "VIEW_BUTTON",
  VIEW_CONTENT = "VIEW_CONTENT",
  VIEW_NAVBAR = "VIEW_NAVBAR",
}

export const userPermissions = [
  Permissions.VIEW_INPUT,
  Permissions.VIEW_BUTTON,
  Permissions.VIEW_CONTENT,
  Permissions.VIEW_NAVBAR,
];
Enter fullscreen mode Exit fullscreen mode

The component will receive an array of Permissions.

// withPermission
type permissions = {
  permissions: Permissions[];
};

ComponentWithPermission: React.FC<T & React.Attributes & permissions>
Enter fullscreen mode Exit fullscreen mode

If you try and use this component you will notice that now permissions prop is required.

Great, now that we have everything in place, the only thing left to do is validate the user permission against the required component permission.

Inside the ComponentWithPermission will add the following logic

// withPermission
 const ComponentWithPermission: React.FC<
    T & React.Attributes & permissions
  > = (props) => {
 +   const hasPermissions = props.permissions.every((value) =>
 +      userPermissions.includes(value)
 +   );

 +   if (hasPermissions) {
 +      return <Component {...props} />;
 +   }

 +   return <React.Fragment />;
 }
Enter fullscreen mode Exit fullscreen mode

Now that we know if the user has the required permission to view the component, we can render the Component, in case they don't we render a Fragment.

Time to use the HOC

Whenever you need a component with permission you wrap them around base component.

import OriginalButton from '@/components/Button'
const Button = withPermission(OriginalButton);

<Button permissions={[Permissions.VIEW_BUTTON]} />
Enter fullscreen mode Exit fullscreen mode

This is should be enough for a basic render your permission based component.

Extra Content

Let's say that instead of not showing the component we want to have them but with a disabled state for example.
We will need to add a few extra props to the creation of our HOC

type Props = {
  disable?: boolean;
}

export function withPermission<T>(Component: React.FC<T>, options?: Props) {
   const ComponentWithPermission: React.FC<T & React.Attributes & permissions> = (props) => {
      const hasPermissions = props.permissions.every((value) =>
        userPermissions.includes(value)
      );

      if (hasPermissions) {
        return <Component {...props} />;
      } else if (!hasPermissions && options?.disable) {
        return (
          <Component
            {...props}
            disabled={options.disable}
            className="opacity-50"
          />
        );
      }

      return <React.Fragment />;
   }
}
Enter fullscreen mode Exit fullscreen mode

Now whenever we define a component, we will have an option props as well.

const Button = withPermission(OriginalButton, { disable: true });
Enter fullscreen mode Exit fullscreen mode

That's it for this tutorial.

💖 💪 🙅 🚩
gdstp
Gabriel Teles

Posted on July 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related