CASL React

This package allows to integrate @casl/ability into React application. So, you can show or hide UI elements based on user ability to see them.
Installation
npm install @casl/react @casl/ability
Getting Started
This package provides Can
component which can be used to conditionally show UI elements based on user abilities.
This component accepts children and 4 properties (see Property names and aliases)
I
(do
is an alias) - name of the action and fielda
(on
, of
, this
are aliases) - checked subjectnot
- checks whether the ability does not allow an actionability
- an instance of Ability
which will be used to check permissions
children
property may be either a render function (a recommended way):
<Can I="create" a="Post" ability={ability}>
{() => <button onClick={this.createPost.bind(this)}>Create Post</button>}
</Can>
or React elements:
<Can I="create" a="Post" ability={ability}>
<button onClick={this.createPost.bind(this)}>Create Post</button>
</Can>
Note: it's better to pass children as a render function just because it will not create additional React elements if user doesn't have ability to do some action (in the case above read Post
)
1. Scoping Can to use a particular ability
Yes, it's a bit inconvenient to pass ability
in every Can
component.
This was actually done for cases when you have several abilities in your app and/or want to restrict a particular Can
component to check abilities using another instance.
There are 2 function which allow to scope Can
to use a particular instance of Ability
:
createCanBoundTo
createContextualCan
The first function just creates a new component which is bound to a particular ability and accepts only 2 properties: do
and on
:
import { createCanBoundTo } from '@casl/react'
import ability from './ability'
export default createCanBoundTo(ability)
Then import bound Can
into any component (now you don't need to pass ability
property):
import Can from './Can'
export function button() {
return (
<Can I="create" a="Post">
{() => <button onClick={this.createPost.bind(this)}>Create Post</button>}
</Can>
)
}
createContextualCan
uses React Context API (available from React@16.3.0) and provides specified ability to all children components.
import { createContext } from 'react'
import { createContextualCan } from '@casl/react'
export const AbilityContext = createContext()
export const Can = createContextualCan(AbilityContext.Consumer)
Later you need to provide your ability to AbilityContext.Provider
import { AbilityContext } from './ability-context'
import ability from './ability'
export default function App({ props }) {
return (
<AbilityContext.Provider value={ability}>
<TodoApp />
</AbilityContext.Provider>
)
}
And inside TodoApp
you can use previously created Can
component:
import React, { Component } from 'react'
import { Can } from './ability-context'
export class TodoApp extends Component {
createTodo() {
}
render() {
return (
<Can I="create" a="Todo">
{() => <button onClick={this.createTodo.bind(this)}>Create Todo</button>}
</Can>
)
}
}
See casl-react-example for more examples.
2. Defining Abilities
There are 2 options how you can define Ability
instance:
- define an empty ability and update it when user login
- define ability using
AbilityBuilder
and optionally update it when user login
To define empty ability:
import { Ability } from '@casl/ability'
export default new Ability([])
To create ability using AbilityBuilder
import { AbilityBuilder } from '@casl/ability'
export default AbilityBuilder.define(can => {
can('read', 'all')
})
Later in your login component:
import React, { Component } from 'react'
import ability from './ability'
export class LoginComponent extends Component {
login(event) {
event.preventDefault()
const { email, password } = this.state
return fetch('path/to/api/login', { method: 'POST', body: JSON.stringify({ email, password }) })
.then(response => response.json())
.then(session => ability.update(session.rules))
}
render() {
return (
<form onSubmit={this.login.bind(this)}>
// ...
</form>
)
}
}
Obviously, in this case your server API should provide the list of user abilities in rules
field of the response.
See @casl/ability package for more information on how to define abilities.
3. Property names and aliases
As you can see from the code above, component name and its property names and values creates an English sentence, basically a question.
For example, the code below reads as Can I create a Post
.
<Can I="create" a="Post">
{() => <button onClick={...}>Create Post</button>}
</Can>
There are several other property aliases which allow to construct a readable question:
- use
a
(or an
) alias when you check by Type
<Can I="read" a="Post">...</Can>
- use
of
alias instead of a
when you check by subject field
<Can I="read title" of="Post">...</Can>
<Can I="read title" of={this.props.post}>...</Can>
- use
this
alias instead of of
and a
when you check action on instance
<Can I="read" this={this.props.post}>...</Can>
- use
do
and on
if you are bored and don't want to make your code more readable :)
<Can do="read" on={this.props.post}>...</Can>
<Can do="read title" on={this.props.post}>...</Can>
- use
not
when you want to invert the render method
<Can not I="read" a="Post">...</Can>
- use
passThrough
if you want to customize behavior of <Can>
component, for example disable button instead of hiding it:
<Can I="read" a="Post" passThrough>
{can => <button disabled={!can}>Save</button>}
</Can>
Want to help?
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for contributing
License
MIT License