• Home
  • Skills
  • Projects
  • Blog
  • Contact
Resume
Naser Rasouli

Author

Naser Rasouli

Front-End developer - sharing lessons learned, notes, and write-ups from real projects.

GitHubLinkedIn

Last posts

BEM Methodology in CSS: predictable naming for clean styles
2026-02-18•1 min read

BEM Methodology in CSS: predictable naming for clean styles

A practical guide to BEM to avoid style conflicts, structure class names, and keep CSS maintainable.

Why console.log After setState Shows the Old Value
2026-02-04•1 min read

Why console.log After setState Shows the Old Value

React batches state updates, so logging right after setState prints the previous value. Here’s why and the right ways to read the fresh state.

Cleanup Functions in useEffect: Stop Leaks Before They Start
2026-02-04•1 min read

Cleanup Functions in useEffect: Stop Leaks Before They Start

A practical guide to writing cleanup in useEffect so you avoid memory leaks, duplicate listeners, and setState on unmounted components.

Static RBAC: Roles & Permissions in React

Static RBAC: Roles & Permissions in React

2025-12-20
reactjsrbacaccess-controlpermissions

Introduction

Building an admin panel means deciding who can see or do what. A simple static approach — roles and permissions defined in code, not fetched from an API — is often enough for small/medium projects. Here’s a clean pattern to guard both routes and UI controls, with room to grow later.

What we’ll cover

  • Define roles, pages, and permissions in one place.
  • Protect routes so only allowed roles can visit them.
  • Hide or show UI actions based on permissions.

1) Define roles and permissions

Keep everything in a single roles.ts to avoid scattered logic.

// roles.ts
export const ROLES = {
  ADMIN: "ADMIN",
  EDITOR: "EDITOR",
  VIEWER: "VIEWER",
} as const;

export const PERMISSIONS = {
  USER_CREATE: "USER_CREATE",
  USER_DELETE: "USER_DELETE",
  USER_VIEW: "USER_VIEW",
} as const;

export const roleAccess = {
  [ROLES.ADMIN]: ["dashboard", "users", "settings"],
  [ROLES.EDITOR]: ["dashboard", "users"],
  [ROLES.VIEWER]: ["dashboard"],
};

export const rolePermissions = {
  [ROLES.ADMIN]: [
    PERMISSIONS.USER_CREATE,
    PERMISSIONS.USER_DELETE,
    PERMISSIONS.USER_VIEW,
  ],
  [ROLES.EDITOR]: [PERMISSIONS.USER_VIEW],
  [ROLES.VIEWER]: [PERMISSIONS.USER_VIEW],
};

You now have one source of truth: which role can visit which pages, and which actions each role can perform.


2) Route-level access with ProtectedRoute

Block navigation if the user’s role isn’t allowed.

// ProtectedRoute.tsx
import { Navigate } from "react-router-dom";
import { useAuth } from "@/hooks/useAuth";
import { roleAccess, ROLES } from "./roles";

export function ProtectedRoute({
  children,
  allowed,
}: {
  children: JSX.Element;
  allowed: string[];
}) {
  const { role } = useAuth();

  if (!role || !allowed.includes(role)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return children;
}

Use it in your routes:

// routes.tsx
import { ProtectedRoute } from "./ProtectedRoute";
import { roleAccess, ROLES } from "./roles";
import DashboardPage from "@/features/dashboard/pages/DashboardPage";
import UsersPage from "@/features/users/pages/UsersPage";

export const routes = [
  {
    path: "/dashboard",
    element: (
      <ProtectedRoute allowed={roleAccess[ROLES.VIEWER]}>
        <DashboardPage />
      </ProtectedRoute>
    ),
  },
  {
    path: "/users",
    element: (
      <ProtectedRoute allowed={roleAccess[ROLES.EDITOR]}>
        <UsersPage />
      </ProtectedRoute>
    ),
  },
];

Only roles declared in roleAccess can reach each page.


3) Component-level access with AccessControl

Hide or show specific UI actions based on permissions.

// AccessControl.tsx
import { ReactNode } from "react";
import { useAuth } from "@/hooks/useAuth";
import { rolePermissions } from "./roles";

type Props = {
  permission: string;
  children: ReactNode;
};

export function AccessControl({ permission, children }: Props) {
  const { role } = useAuth();
  if (!role) return null;

  const permissions = rolePermissions[role] || [];
  return permissions.includes(permission) ? <>{children}</> : null;
}

Example usage inside a page:

import { AccessControl } from "@/components/AccessControl";
import { PERMISSIONS } from "@/routes/roles";

function UsersPage() {
  return (
    <div>
      <h1>User List</h1>

      <AccessControl permission={PERMISSIONS.USER_CREATE}>
        <button>Add User</button>
      </AccessControl>

      <AccessControl permission={PERMISSIONS.USER_DELETE}>
        <button>Delete User</button>
      </AccessControl>
    </div>
  );
}

Summary

  • Define roles, routes, and permissions in one module (roles.ts).
  • Use ProtectedRoute to guard navigation.
  • Use AccessControl to gate UI actions.
  • Start static; later you can swap roleAccess and rolePermissions with API-driven data.

Conclusion

With a few lightweight helpers, you get predictable, centralized access control that keeps your admin UI clean today and flexible enough to go dynamic tomorrow. Happy coding! 🚀