DEV Community

Volodymyr Yepishev
Volodymyr Yepishev

Posted on

Implementing React Check Permissions: the Hook

You can follow the code in this article in the commit in the repo I made for the series.

Continuing our series on implementing permission checking tools in React application, in this article we take a look at the check permissions hook and how to implement it.

The hook is actually the place where the logic of obtaining current permissions can be placed, then it can expose a method for checking permissions in a way that does not require components to bother with getting active user permissions from the profile or whatever.

Let us create a folder called models and place the types for our future hook:

// ./permissions-validation/models/use-check-permissions.ts
export type UseCheckPermissions = () => UseCheckPermissionsMethods;

export interface UseCheckPermissionsMethods {
    checkPermissions: (
        permissions?: string[] | string | null,
        checkAll?: boolean
    ) => boolean;
}
Enter fullscreen mode Exit fullscreen mode

Our future hook is going to be of type UseCheckPermissions, a function that accepts no parameters, but returns an object with a method for permission validation.

At this point you might start thinking how do are we supposed to design our hook for obtaining active permissions when we have neither a user profile nor a slightest idea how and where these current permissions are going to be stored. The best part about it is that we don't have to know. Otherwise our permission-validation module would become coupled with permission storing mechanism used in the application. This is something we should, can and will avoid.

Functional approach and factory method come to the rescue here. Instead of actually implementing a hook which would know a way how to obtain current permissions, we will make a factory to produce it and pass a function for retrieving current permissions to it. This way the hook will have no idea where do permissions come from, which is great.

So let's add a type for a function that would give us current user permissions:

// ./permissions-validation/models/get-permissions.ts
export type GetPermissions = () => string[];
Enter fullscreen mode Exit fullscreen mode

Now an index file in models folder for the convenience of exports and we are ready to build our hook factory!

// ./permissions-validation/models/index.ts
export * from "./get-permissions";
export * from "./use-check-permissions";
Enter fullscreen mode Exit fullscreen mode

Our hook factory is going to live in create-check-permissions-hook folder next to an index file for exports and a file with tests.

// ./permissions-validation/create-check-permissions-hook/create-check-permissions-hook.function.ts
import { checkPermissions } from "../check-permissions";
import { GetPermissions, UseCheckPermissions } from "../models";

export function createCheckPermissionsHook(
    getCurrentPermissions: GetPermissions
): UseCheckPermissions {
    return () => ({
        checkPermissions: (
            permissions?: string[] | string | null,
            checkAll = true
        ): boolean => checkPermissions(getCurrentPermissions(), permissions, checkAll),
    });
}
Enter fullscreen mode Exit fullscreen mode

So we expect to be given a function for obtaining current user permissions and return a hook exposing checkPermissions method, which in its term invokes the checkPermissions function from the previous article.

To ensure everything works as expected, we can now add some test cases, which are basically a copy of checkPermissions function tests, but altered so they apply to our hook. Note, that in order to test hooks we are going to need a special package, @testing-library/react-hooks/dom.

// ./permissions-validation/create-check-permissions-hook/create-check-permissions-hook.function.spec.ts
import { renderHook } from "@testing-library/react-hooks/dom";

import { createCheckPermissionsHook } from "./create-check-permissions-hook.function";

describe("Tests for createCheckPermissionsHook factory and its hook", () => {
    let checkPermissions: (
        permissions?: string[] | string | null,
        checkAll?: boolean
    ) => boolean;

    beforeEach(() => {
        const { result } = renderHook(
            createCheckPermissionsHook(() => ["some-view-permission"])
        );
        checkPermissions = result.current.checkPermissions;
    });

    it("The hook should be created", () => {
        expect(checkPermissions).toBeTruthy();
    });

    it("Result should be positive if no required permissions provided", () => {
        // Arrange
        const currentPermissions: string[] = [];

        // Act
        const hasPermissions = checkPermissions(currentPermissions);

        // Assert
        expect(hasPermissions).toBeTruthy();
    });

    it("Result should be positive if required permissions are present in current permissions", () => {
        // Arrange
        const requiredPermission = "some-view-permission";

        // Act
        const hasPermissions = checkPermissions(requiredPermission);

        // Assert
        expect(hasPermissions).toBeTruthy();
    });

    it("Result should be negative if not all required permissions are present", () => {
        // Arrange
        const requiredPermission = ["some-view-permission", "some-other-permission"];

        // Act
        const hasPermissions = checkPermissions(requiredPermission);

        // Assert
        expect(hasPermissions).toBeFalsy();
    });

    it("Result should be positive if not all required permissions are present when checkAll parameter is set to false", () => {
        // Arrange
        const requiredPermission = ["some-view-permission", "some-other-permission"];

        // Act
        const hasPermissions = checkPermissions(requiredPermission, false);

        // Assert
        expect(hasPermissions).toBeTruthy();
    });
});

Enter fullscreen mode Exit fullscreen mode

The function powering the hook, which in its turn is going to be powering the wrapper component we will be creating in the next article :)

Discussion (0)