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]} />
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;
}
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,
];
The component will receive an array of Permissions.
// withPermission
type permissions = {
permissions: Permissions[];
};
ComponentWithPermission: React.FC<T & React.Attributes & permissions>
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 />;
}
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]} />
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 />;
}
}
Now whenever we define a component, we will have an option props as well.
const Button = withPermission(OriginalButton, { disable: true });
That's it for this tutorial.
Top comments (0)