React Integration
Fire Shield provides hooks and components for React applications.
Installation
bash
npm install @fire-shield/react @fire-shield/coreSetup
Basic Setup
typescript
// App.tsx
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
import { useState } from 'react'
// Initialize RBAC
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
function App() {
const [user, setUser] = useState({
id: '1',
roles: ['editor']
})
return (
<RBACProvider rbac={rbac} user={user}>
<YourApp />
</RBACProvider>
)
}Hooks
useRBAC
Access RBAC functionality in your components:
tsx
import { useRBAC } from '@fire-shield/react'
function PostEditor() {
const { can, cannot, hasRole, user } = useRBAC()
const handleDelete = () => {
if (!can('posts:delete')) {
alert('No permission to delete')
return
}
deletePost()
}
return (
<div>
<h2>Post Editor</h2>
<p>Current user: {user?.id}</p>
{can('posts:write') && (
<button onClick={createPost}>Create Post</button>
)}
{can('posts:delete') && (
<button onClick={handleDelete}>Delete Post</button>
)}
{cannot('posts:publish') && (
<p>Contact admin to publish posts</p>
)}
{hasRole('admin') && (
<button onClick={openAdminPanel}>Admin Panel</button>
)}
</div>
)
}Hook API
typescript
interface UseRBACReturn {
// Check if user has permission
can: (permission: string) => boolean
// Check if user doesn't have permission
cannot: (permission: string) => boolean
// Check if user has role
hasRole: (role: string) => boolean
// Current user
user: RBACUser | null
// Update current user
setUser: (user: RBACUser | null) => void
// RBAC instance
rbac: RBAC
}Components
Can Component
Conditionally render content based on permissions:
tsx
import { Can } from '@fire-shield/react'
function PostActions() {
return (
<div>
<Can permission="posts:write">
<button>Create Post</button>
</Can>
<Can permission="posts:delete" fallback={<p>No permission</p>}>
<button>Delete Post</button>
</Can>
</div>
)
}Cannot Component
Inverse of Can component:
tsx
import { Cannot } from '@fire-shield/react'
function UpgradePrompt() {
return (
<Cannot permission="premium:access">
<div className="upgrade-banner">
<p>Upgrade to unlock premium features</p>
<button>Upgrade Now</button>
</div>
</Cannot>
)
}RequirePermission Component
Throw error or show fallback if permission is missing:
tsx
import { RequirePermission } from '@fire-shield/react'
function AdminPanel() {
return (
<RequirePermission
permission="admin:access"
fallback={<div>Access Denied</div>}
>
<div>
<h1>Admin Panel</h1>
{/* Admin content */}
</div>
</RequirePermission>
)
}RequireRole Component
Require specific role:
tsx
import { RequireRole } from '@fire-shield/react'
function ModeratorTools() {
return (
<RequireRole
role="moderator"
fallback={<div>Moderator access required</div>}
>
<div>
<h2>Moderator Tools</h2>
{/* Moderator tools */}
</div>
</RequireRole>
)
}React Router Integration
Protected Routes
tsx
import { Navigate } from 'react-router-dom'
import { useRBAC } from '@fire-shield/react'
function ProtectedRoute({ permission, children }) {
const { can } = useRBAC()
if (!can(permission)) {
return <Navigate to="/unauthorized" replace />
}
return children
}
// Usage in router
<Routes>
<Route
path="/admin"
element={
<ProtectedRoute permission="admin:access">
<AdminPage />
</ProtectedRoute>
}
/>
</Routes>Route Guards
tsx
import { createBrowserRouter } from 'react-router-dom'
import { useRBAC } from '@fire-shield/react'
const router = createBrowserRouter([
{
path: '/admin',
element: <AdminLayout />,
loader: () => {
const { can } = useRBAC()
if (!can('admin:access')) {
throw new Response('Unauthorized', { status: 403 })
}
return null
},
children: [
{ path: 'users', element: <UsersPage /> },
{ path: 'settings', element: <SettingsPage /> }
]
}
])Updating User
Update user permissions dynamically:
tsx
import { useRBAC } from '@fire-shield/react'
function UserSwitcher() {
const { user, setUser } = useRBAC()
const switchToAdmin = () => {
setUser({
id: 'admin-1',
roles: ['admin']
})
}
const switchToViewer = () => {
setUser({
id: 'viewer-1',
roles: ['viewer']
})
}
return (
<div>
<p>Current user: {user?.id}</p>
<button onClick={switchToAdmin}>Switch to Admin</button>
<button onClick={switchToViewer}>Switch to Viewer</button>
</div>
)
}TypeScript Support
Full TypeScript support with type inference:
typescript
import { useRBAC } from '@fire-shield/react'
import type { RBACUser } from '@fire-shield/core'
// Type-safe user
const user: RBACUser = {
id: 'user-123',
roles: ['editor']
}
// Type-safe hook
const { can, hasRole } = useRBAC()
can('posts:write') // Type-checked
hasRole('editor') // Type-checkedBest Practices
1. Use Components for JSX
tsx
// ✅ Good: Declarative and clean
<Can permission="posts:delete">
<button>Delete</button>
</Can>
// ❌ Avoid: Less readable
{can('posts:delete') && <button>Delete</button>}2. Use Hooks for Logic
tsx
// ✅ Good: Clear permission check
const { can } = useRBAC()
const handleDelete = () => {
if (!can('posts:delete')) {
showError('No permission')
return
}
deletePost()
}
// ❌ Avoid: Permission check in JSX
<button onClick={() => {
if (!can('posts:delete')) return
deletePost()
}}>3. Memoize Permission Checks
tsx
import { useMemo } from 'react'
import { useRBAC } from '@fire-shield/react'
function ExpensiveComponent() {
const { can } = useRBAC()
const permissions = useMemo(() => ({
canWrite: can('posts:write'),
canDelete: can('posts:delete'),
canPublish: can('posts:publish')
}), [can])
return (
// Use permissions object
)
}4. Handle Loading States
tsx
function UserProfile() {
const { user, can } = useRBAC()
if (!user) {
return <div>Loading...</div>
}
return (
<div>
<h1>{user.id}</h1>
{can('profile:edit') && <EditButton />}
</div>
)
}Server-Side Rendering (SSR)
Next.js Integration
For Next.js, use the dedicated @fire-shield/next package which provides server-side support.
Other SSR Frameworks
Provide initial user state from server:
tsx
// server.tsx
const initialUser = await getUserFromSession(req)
const html = renderToString(
<RBACProvider rbac={rbac} user={initialUser}>
<App />
</RBACProvider>
)Testing
Testing Components with RBAC
tsx
import { render, screen } from '@testing-library/react'
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
function renderWithRBAC(component, user) {
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*'])
rbac.createRole('viewer', ['posts:read'])
return render(
<RBACProvider rbac={rbac} user={user}>
{component}
</RBACProvider>
)
}
test('shows delete button for admin', () => {
renderWithRBAC(
<PostActions />,
{ id: '1', roles: ['admin'] }
)
expect(screen.getByText('Delete Post')).toBeInTheDocument()
})
test('hides delete button for viewer', () => {
renderWithRBAC(
<PostActions />,
{ id: '2', roles: ['viewer'] }
)
expect(screen.queryByText('Delete Post')).not.toBeInTheDocument()
})Examples
Blog Post Management
tsx
import { Can, useRBAC } from '@fire-shield/react'
function PostCard({ post }) {
const { can } = useRBAC()
return (
<div className="post-card">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<div className="actions">
<Can permission="posts:read">
<button>Read More</button>
</Can>
<Can permission="posts:write">
<button>Edit</button>
</Can>
<Can permission="posts:delete">
<button>Delete</button>
</Can>
{can('posts:publish') && !post.published && (
<button>Publish</button>
)}
</div>
</div>
)
}User Management Dashboard
tsx
import { RequirePermission, Can } from '@fire-shield/react'
function UserManagement() {
return (
<RequirePermission permission="users:read">
<div>
<h1>User Management</h1>
<Can permission="users:create">
<button>Add New User</button>
</Can>
<UserList />
<Can permission="users:export">
<button>Export Users</button>
</Can>
</div>
</RequirePermission>
)
}Next Steps
- Explore API Reference
- Learn about Permissions
- Check out Examples
