Skip to Content
DocumentationReact Hooks (NEW)

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-react

Setup

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> ) }
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

  1. 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')
  2. Prefer Specific Hooks
    Use useCan and useCannot for simple boolean checks:
    // ✅ Good - clear and concise const { can: canEdit } = useCan('edit', 'Post') // ❌ More verbose const { isAllowed } = useAbility() const canEdit = isAllowed('edit', 'Post')
  3. 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 to isAllowed, notAllowed, can, cannot, getReason and throwIfNotAllowed methods
  • useCan: Simple hook which returns an object of can and reason. can is a simple boolean check for whether an action is allowed, and the reason describes the why.
  • useCannot: Simple hook which returns an object of can and reason. can is a simple boolean check for whether an action is disallowed, and the reason describes the why.
  • useReason: Returns the reason, if any, explaining why an action is allowed or not
  • useThrowIfNotAllowed: 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.

Last updated on