React Hooks
The @nordic-ui/asgardian-react package provides React hooks that make it easy to work with permissions in your React applications. These hooks integrate seamlessly with the Asgardian ability system and provide a convenient way to check permissions in your components.
Installation
npm install @nordic-ui/asgardian-reactSetup
First, you need to set up the AbilityProvider at the root of your application:
import { createAbility } from '@nordic-ui/asgardian'
import { AbilityProvider } from '@nordic-ui/asgardian-react'
// Create your ability instance
const ability = createAbility()
.can('read', 'Post')
.can('write', 'Comment')
.can('manage', 'Profile')
const App = () => {
return (
<AbilityProvider ability={ability}>
<YourApp />
</AbilityProvider>
)
}Available Hooks
useAbility
The core hook that provides access to the ability instance and its methods.
import { useAbility } from '@nordic-ui/asgardian-react'
const MyComponent = () => {
const { isAllowed, notAllowed } = useAbility()
if (isAllowed('read', 'Post')) {
return <div>You can read posts!</div>
}
if (notAllowed('write', 'Post')) {
return <div>You cannot write posts</div>
}
return null
}Type Safety
You can provide type parameters for better TypeScript support:
type Actions = 'read' | 'write' | 'delete' | 'manage'
type Resources = 'Post' | 'Comment' | 'User'
const TypedComponent = () => {
const { isAllowed, notAllowed } = useAbility<Actions, Resources>()
// TypeScript will enforce correct action and resource types
const canRead = isAllowed('read', 'Post') // ✅ Valid
const canEdit = isAllowed('edit', 'Post') // ❌ TypeScript error
}Working with Conditions
type PostComponentProps = {
post: { id: number, authorId: number }
}
const PostComponent: FC<PostComponentProps> = ({ post }) => {
const { isAllowed } = useAbility()
const canEdit = isAllowed('edit', 'Post', { authorId: post.authorId })
const canDelete = isAllowed('delete', 'Post', post)
return (
<div>
<h1>{post.title}</h1>
{canEdit && <button>Edit</button>}
{canDelete && <button>Delete</button>}
</div>
)
}Multiple Actions or Resources
const Dashboard = () => {
const { isAllowed } = useAbility()
// Check multiple actions (returns true if ANY action is allowed)
const canModify = isAllowed(['edit', 'delete'], 'Post')
// Check multiple resources (returns true if action is allowed on ANY resource)
const canRead = isAllowed('read', ['Post', 'Comment'])
// Check multiple actions on multiple resources
const hasAnyAccess = isAllowed(['read', 'write'], ['Post', 'Comment'])
return (
<div>
{canModify && <button>Modify Content</button>}
{canRead && <div>Content available</div>}
</div>
)
}Working with Reasons
Access permission reasons for better user feedback:
const PermissionAwareComponent = () => {
const { throwIfNotAllowed } = useAbility()
const { can: canDelete, reason: deleteReason } = useCan('delete', 'Post')
const handleDelete = () => {
try {
throwIfNotAllowed('delete', 'Post')
// Proceed with deletion
} catch (error) {
if (error instanceof ForbiddenError) {
toast.error(error.message)
}
}
}
return (
<div>
<button onClick={handleDelete} disabled={!canDelete}>
Delete
</button>
{!canDelete && deleteReason && (
<p className="text-red-500">{deleteReason}</p>
)}
</div>
)
}useCan
A convenient hook that returns a boolean indicating whether a specific action is allowed on a resource.
import { useCan } from '@nordic-ui/asgardian-react'
type PostActionsProps = { post: Post }
const PostActions: FC<PostActionsProps> = ({ post }) => {
const { can: canEdit } = useCan('edit', 'Post', { authorId: post.authorId })
const { can: canDelete } = useCan('delete', 'Post', post)
const { can: canPublish } = useCan('publish', 'Post')
return (
<div>
{canEdit && <button>Edit</button>}
{canDelete && <button>Delete</button>}
{canPublish && <button>Publish</button>}
</div>
)
}With Type Safety
type Actions = 'read' | 'write' | 'delete' | 'publish'
type Resources = 'Article' | 'Comment' | 'User'
const TypedPostActions = () => {
const { can: canWrite } = useCan<Actions, Resources>('write', 'Article')
const { can: canDelete } = useCan<Actions, Resources>('delete', 'Comment')
return (
<div>
{canWrite && <button>Write Article</button>}
{canDelete && <button>Delete Comment</button>}
</div>
)
}useCannot
The inverse of useCan - returns true when an action is not allowed.
import { useCannot } from '@nordic-ui/asgardian-react'
const RestrictedContent = () => {
const { cannot: cannotView } = useCannot('view', 'PremiumContent')
const { cannot: cannotEdit } = useCannot('edit', 'Post')
if (cannotView) {
return <div>Upgrade to premium to view this content</div>
}
return (
<div>
<h1>Premium Content</h1>
{cannotEdit && (
<p>You don't have permission to edit this post</p>
)}
</div>
)
}Practical Examples
Conditional Rendering
type BlogPostProps = { post: Post }
const BlogPost: FC<BlogPostProps> = ({ post }) => {
const { can: canEdit } = useCan('edit', 'Post', { authorId: post.authorId })
const { can: canDelete } = useCan('delete', 'Post', { authorId: post.authorId })
const { cannot: cannotComment } = useCannot('create', 'Comment')
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* Action buttons */}
<div className="actions">
{canEdit && (
<button onClick={() => editPost(post.id)}>
Edit
</button>
)}
{canDelete && (
<button onClick={() => deletePost(post.id)}>
Delete
</button>
)}
</div>
{/* Comments section */}
{cannotComment ? (
<p>Sign in to leave a comment</p>
) : (
<CommentForm postId={post.id} />
)}
</article>
)
}Navigation Guards
const Navigation = () => {
const { can: canViewAdmin } = useCan('access', 'AdminPanel')
const { can: canManageUsers } = useCan('manage', 'User')
const { can: canViewReports } = useCan('view', 'Reports')
return (
<nav>
<Link href="/">Home</Link>
<Link href="/posts">Posts</Link>
{canViewAdmin && (
<div className="admin-section">
<Link href="/admin">Admin</Link>
{canManageUsers && <Link href="/admin/users">Users</Link>}
{canViewReports && <Link href="/admin/reports">Reports</Link>}
</div>
)}
</nav>
)
}Form Permissions
type UserFormProps = { user: User }
const UserForm: FC<UserFormProps> = ({ user }) => {
const { isAllowed } = useAbility()
const { can: canEditProfile } = useCan('edit', 'Profile', { userId: user.id })
const { can: canChangeRole } = useCan('change', 'UserRole')
const { cannot: cannotDeactivate } = useCannot('deactivate', 'User', { userId: user.id })
return (
<form>
<input
name="name"
defaultValue={user.name}
disabled={!canEditProfile}
/>
<input
name="email"
defaultValue={user.email}
disabled={!canEditProfile}
/>
{canChangeRole && (
<select name="role" defaultValue={user.role}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
)}
{cannotDeactivate && (
<p className="warning">
You cannot deactivate this user
</p>
)}
<button
type="submit"
disabled={!isAllowed(['edit', 'manage'], 'User')}
>
Save Changes
</button>
</form>
)
}Dynamic Ability Updates
The hooks automatically respond to changes in the ability instance:
const App = () => {
const [user, setUser] = useState(null)
const [ability, setAbility] = useState(() => createAbility())
useEffect(() => {
if (user) {
// Update ability when user changes
const newAbility = createUserAbility(user)
setAbility(newAbility)
}
}, [user])
return (
<AbilityProvider ability={ability}>
<Dashboard />
</AbilityProvider>
)
}
const Dashboard = () => {
const { can: canCreatePost } = useCan('create', 'Post')
// This will automatically update when the ability changes
return (
<div>
{canCreatePost && <button>Create Post</button>}
</div>
)
}Error Handling
The hooks will throw errors if used outside of an AbilityProvider:
const ComponentWithoutProvider = () => {
// ❌ This will throw an error
const { can: canRead } = useCan('read', 'Post')
return <div>This won't work</div>
}
// Error: "useAbilityContext must be used within an AbilityProvider"Make sure to wrap your app with AbilityProvider:
// ✅ Correct usage
const App = () => {
const ability = createAbility()
return (
<AbilityProvider ability={ability}>
<ComponentWithProvider />
</AbilityProvider>
)
}Best Practices
- Use Type Parameters
Always provide type parameters for better TypeScript support:type Actions = 'read' | 'write' | 'delete' type Resources = 'Post' | 'Comment' // ✅ Good const { can: canWrite } = useCan<Actions, Resources>('write', 'Post') // ❌ Less ideal - no type safety const { can: canWrite } = useCan('write', 'Post') - Prefer Specific Hooks
UseuseCananduseCannotfor simple boolean checks:// ✅ Good - clear and concise const { can: canEdit } = useCan('edit', 'Post') // ❌ More verbose const { isAllowed } = useAbility() const canEdit = isAllowed('edit', 'Post') - Extract Permission Logic
For complex permission logic, consider extracting it into custom hooks:const usePostPermissions = (post: Post) => { const { can: canRead } = useCan('read', 'Post', post) const { can: canEdit } = useCan('edit', 'Post', { authorId: post.authorId }) const { can: canDelete } = useCan('delete', 'Post', { authorId: post.authorId }) const { can: canPublish } = useCan('publish', 'Post') return { canRead, canEdit, canDelete, canPublish, canModify: canEdit || canDelete } } type PostCardProps = { post: Post } // Usage const PostCard: FC<PostCardProps> = ({ post }) => { const { canEdit, canDelete, canPublish } = usePostPermissions(post) return ( <div> {/* Use the extracted permissions */} </div> ) }
Summary
The @nordic-ui/asgardian-react hooks provide a powerful and type-safe way to integrate permissions into your React applications:
useAbility: Core hook providing access toisAllowed,notAllowed,can,cannot,getReasonandthrowIfNotAllowedmethodsuseCan: Simple hook which returns an object ofcanandreason.canis a simple boolean check for whether an action is allowed, and thereasondescribes the why.useCannot: Simple hook which returns an object ofcanandreason.canis a simple boolean check for whether an action is disallowed, and thereasondescribes the why.useReason: Returns the reason, if any, explaining why an action is allowed or notuseThrowIfNotAllowed: Simple boolean check for whether an action is not allowed
These hooks work seamlessly with the Asgardian permission system and automatically update when abilities change, making them perfect for building dynamic, permission-aware user interfaces.