New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@casl/react

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@casl/react - npm Package Compare versions

Comparing version 1.0.4 to 2.0.0

contextApiPatch.d.ts

24

CHANGELOG.md

@@ -5,2 +5,26 @@ # Change Log

# [2.0.0](https://github.com/stalniy/casl/compare/@casl/react@1.0.4...@casl/react@2.0.0) (2020-04-09)
### Bug Fixes
* **react:** makes sure `Can` infers types for props from provided `Ability` ([5813b25](https://github.com/stalniy/casl/commit/5813b25d286af6ff76bec6c266fe21af817fe45b)), closes [#248](https://github.com/stalniy/casl/issues/248)
### chore
* **react:** replaces npmignore with `files` field in package.json ([967b2de](https://github.com/stalniy/casl/commit/967b2deac08387b7cf235a22dc47ea05c49f59eb))
### Features
* **react:** adds generics for Ability and related components [skip ci] ([3102b6e](https://github.com/stalniy/casl/commit/3102b6e639213553570cf97661b7b7f4c3640687)), closes [#256](https://github.com/stalniy/casl/issues/256)
* **react:** adds support for action only components ([a2db577](https://github.com/stalniy/casl/commit/a2db577910763ecc0b5eca3196bdc22fc8ff3dba)), closes [#107](https://github.com/stalniy/casl/issues/107)
* **vue:** adds better generics typying for Vue ([5cc7b60](https://github.com/stalniy/casl/commit/5cc7b60d8a2a53db217f8ad1a4673a28f67aefce)), closes [#107](https://github.com/stalniy/casl/issues/107)
### BREAKING CHANGES
* **react:** hint to note breaking changes
# [@casl/react-v1.0.4](https://github.com/stalniy/casl/compare/@casl/react@1.0.3...@casl/react@1.0.4) (2019-07-28)

@@ -7,0 +31,0 @@

204

dist/es5m/index.js

@@ -1,202 +0,2 @@

import React, { Fragment, createElement, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Ability } from '@casl/ability';
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var noop = function noop() {};
var _renderChildren = Fragment ? function (children) {
if (!children) {
return null;
}
return children.length > 1 ? createElement.apply(null, [Fragment, null].concat(children)) : React.Children.only(children);
} : React.Children.only;
var propTypes = {};
if (process.env.NODE_ENV !== 'production') {
var REQUIRED_OBJECT_OR_STRING = PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired;
var alias = function alias(names, validate) {
return function (props) {
// eslint-disable-line
if (!names.split(' ').some(function (name) {
return props[name];
})) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return validate.apply(void 0, [props].concat(args));
}
};
};
propTypes = {
I: alias('do', PropTypes.string.isRequired),
a: alias('on this of an', REQUIRED_OBJECT_OR_STRING),
an: alias('on this of a', REQUIRED_OBJECT_OR_STRING),
of: alias('on a this an', REQUIRED_OBJECT_OR_STRING),
this: alias('on a of an', REQUIRED_OBJECT_OR_STRING),
do: alias('I', PropTypes.string.isRequired),
on: alias('this a of an', REQUIRED_OBJECT_OR_STRING),
not: PropTypes.bool,
passThrough: PropTypes.bool,
children: PropTypes.any.isRequired,
ability: PropTypes.instanceOf(Ability).isRequired
};
}
var Can =
/*#__PURE__*/
function (_PureComponent) {
_inheritsLoose(Can, _PureComponent);
function Can() {
var _this;
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
_this = _PureComponent.call.apply(_PureComponent, [this].concat(args)) || this;
_this.unsubscribeFromAbility = noop;
_this._isAllowed = false;
_this._ability = null;
return _this;
}
var _proto = Can.prototype;
_proto.componentWillUnmount = function componentWillUnmount() {
this.unsubscribeFromAbility();
};
_proto.connectToAbility = function connectToAbility(ability) {
var _this2 = this;
if (ability === this._ability) {
return;
}
this.unsubscribeFromAbility();
this._ability = null;
if (ability) {
this._ability = ability;
this.unsubscribeFromAbility = ability.on('updated', function () {
return _this2.forceUpdate();
});
}
};
_proto.isAllowed = function isAllowed() {
var params = this.props;
var _split = (params.I || params.do).split(/\s+/),
action = _split[0],
field = _split[1];
var subject = params.of || params.a || params.an || params.this || params.on;
var can = params.not ? 'cannot' : 'can';
return params.ability[can](action, subject, field);
};
_proto.render = function render() {
this.connectToAbility(this.props.ability);
this._isAllowed = this.isAllowed();
return this.props.passThrough || this._isAllowed ? this.renderChildren() : null;
};
_proto.renderChildren = function renderChildren() {
var _this$props = this.props,
children = _this$props.children,
ability = _this$props.ability;
var elements = typeof children === 'function' ? children(this._isAllowed, ability) : children;
return _renderChildren(elements);
};
_createClass(Can, [{
key: "allowed",
get: function get() {
return this._isAllowed;
}
}]);
return Can;
}(PureComponent);
_defineProperty(Can, "propTypes", propTypes);
function createCanBoundTo(ability) {
var _class, _temp;
return _temp = _class =
/*#__PURE__*/
function (_Can) {
_inheritsLoose(BoundCan, _Can);
function BoundCan() {
return _Can.apply(this, arguments) || this;
}
return BoundCan;
}(Can), _defineProperty(_class, "propTypes", Object.assign({}, Can.propTypes, {
ability: PropTypes.instanceOf(Ability)
})), _defineProperty(_class, "defaultProps", {
ability: ability
}), _temp;
}
function createContextualCan(Consumer) {
return function ContextualCan(props) {
return createElement(Consumer, null, function (ability) {
return createElement(Can, {
ability: props.ability || ability,
I: props.I || props.do,
a: props.on || props.a || props.an || props.of || props.this,
not: props.not,
children: props.children,
passThrough: props.passThrough
});
});
};
}
export { Can, createCanBoundTo, createContextualCan };
import t,{Fragment as n,createElement as r,PureComponent as i}from"react";function u(t,n){for(var r=0;r<n.length;r++){var i=n[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function o(){return(o=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t}).apply(this,arguments)}function c(t,n){t.prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n}var e=function(){},f=n?function(i){return i?i.length>1?r.apply(void 0,[n,null].concat(i)):t.Children.only(i):null}:t.Children.only,h=function(t){function n(){for(var n,r=arguments.length,i=new Array(r),u=0;u<r;u++)i[u]=arguments[u];return(n=t.call.apply(t,[this].concat(i))||this).t=!1,n.i=null,n.u=e,n}c(n,t);var r,i,o,h=n.prototype;return h.componentWillUnmount=function(){this.u()},h.o=function(t){var n=this;t!==this.i&&(this.u(),this.i=null,t&&(this.i=t,this.u=t.on("updated",(function(){return n.forceUpdate()}))))},h.h=function(){var t=this.props,n=t.of||t.a||t.an||t.this||t.on,r=t.not?"cannot":"can";return t.ability[r](t.I||t.do,n,t.field)},h.render=function(){return this.o(this.props.ability),this.t=this.h(),this.props.passThrough||this.t?this.s():null},h.s=function(){var t=this.props,n=t.children,r=t.ability,i="function"==typeof n?n(this.t,r):n;return f(i)},r=n,(i=[{key:"allowed",get:function(){return this.t}}])&&u(r.prototype,i),o&&u(r,o),n}(i);function s(t){var n,r;return r=n=function(t){function n(){return t.apply(this,arguments)||this}return c(n,t),n}(h),n.defaultProps={ability:t},r}function a(t){return function(n){return r(t,null,(function(t){return r(h,o({ability:t},n))}))}}export{h as Can,s as createCanBoundTo,a as createContextualCan};
//# sourceMappingURL=index.js.map

@@ -1,135 +0,2 @@

import React, { PureComponent, Fragment, createElement } from 'react';
import PropTypes from 'prop-types';
import { Ability } from '@casl/ability';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
const noop = () => {};
const renderChildren = Fragment ? children => {
if (!children) {
return null;
}
return children.length > 1 ? createElement.apply(null, [Fragment, null].concat(children)) : React.Children.only(children);
} : React.Children.only;
let propTypes = {};
if (process.env.NODE_ENV !== 'production') {
const REQUIRED_OBJECT_OR_STRING = PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired;
const alias = (names, validate) => (props, ...args) => {
// eslint-disable-line
if (!names.split(' ').some(name => props[name])) {
return validate(props, ...args);
}
};
propTypes = {
I: alias('do', PropTypes.string.isRequired),
a: alias('on this of an', REQUIRED_OBJECT_OR_STRING),
an: alias('on this of a', REQUIRED_OBJECT_OR_STRING),
of: alias('on a this an', REQUIRED_OBJECT_OR_STRING),
this: alias('on a of an', REQUIRED_OBJECT_OR_STRING),
do: alias('I', PropTypes.string.isRequired),
on: alias('this a of an', REQUIRED_OBJECT_OR_STRING),
not: PropTypes.bool,
passThrough: PropTypes.bool,
children: PropTypes.any.isRequired,
ability: PropTypes.instanceOf(Ability).isRequired
};
}
class Can extends PureComponent {
constructor(...args) {
super(...args);
this.unsubscribeFromAbility = noop;
this._isAllowed = false;
this._ability = null;
}
componentWillUnmount() {
this.unsubscribeFromAbility();
}
connectToAbility(ability) {
if (ability === this._ability) {
return;
}
this.unsubscribeFromAbility();
this._ability = null;
if (ability) {
this._ability = ability;
this.unsubscribeFromAbility = ability.on('updated', () => this.forceUpdate());
}
}
get allowed() {
return this._isAllowed;
}
isAllowed() {
const params = this.props;
const [action, field] = (params.I || params.do).split(/\s+/);
const subject = params.of || params.a || params.an || params.this || params.on;
const can = params.not ? 'cannot' : 'can';
return params.ability[can](action, subject, field);
}
render() {
this.connectToAbility(this.props.ability);
this._isAllowed = this.isAllowed();
return this.props.passThrough || this._isAllowed ? this.renderChildren() : null;
}
renderChildren() {
const {
children,
ability
} = this.props;
const elements = typeof children === 'function' ? children(this._isAllowed, ability) : children;
return renderChildren(elements);
}
}
_defineProperty(Can, "propTypes", propTypes);
function createCanBoundTo(ability) {
var _class, _temp;
return _temp = _class = class BoundCan extends Can {}, _defineProperty(_class, "propTypes", Object.assign({}, Can.propTypes, {
ability: PropTypes.instanceOf(Ability)
})), _defineProperty(_class, "defaultProps", {
ability
}), _temp;
}
function createContextualCan(Consumer) {
return function ContextualCan(props) {
return createElement(Consumer, null, ability => createElement(Can, {
ability: props.ability || ability,
I: props.I || props.do,
a: props.on || props.a || props.an || props.of || props.this,
not: props.not,
children: props.children,
passThrough: props.passThrough
}));
};
}
export { Can, createCanBoundTo, createContextualCan };
import t,{Fragment as n,createElement as i,PureComponent as s}from"react";const e=()=>{},r=n?s=>s?s.length>1?i(n,null,...s):t.Children.only(s):null:t.Children.only;class l extends s{constructor(...t){super(...t),this.t=!1,this.i=null,this.s=e}componentWillUnmount(){this.s()}l(t){t!==this.i&&(this.s(),this.i=null,t&&(this.i=t,this.s=t.on("updated",()=>this.forceUpdate())))}get allowed(){return this.t}h(){const t=this.props,n=t.of||t.a||t.an||t.this||t.on,i=t.not?"cannot":"can";return t.ability[i](t.I||t.do,n,t.field)}render(){return this.l(this.props.ability),this.t=this.h(),this.props.passThrough||this.t?this.o():null}o(){const{children:t,ability:n}=this.props,i="function"==typeof t?t(this.t,n):t;return r(i)}}function h(t){var n,i;return i=n=class extends l{},n.defaultProps={ability:t},i}function o(t){return n=>i(t,null,t=>i(l,{ability:t,...n}))}export{l as Can,h as createCanBoundTo,o as createContextualCan};
//# sourceMappingURL=index.js.map

@@ -1,1 +0,2 @@

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("react"),require("prop-types"),require("@casl/ability")):"function"==typeof define&&define.amd?define(["exports","react","prop-types","@casl/ability"],e):e(((t=t||self).casl=t.casl||{},t.casl.react={}),t.React,t.React.PropTypes,t.casl)}(this,function(t,n,i,r){"use strict";var e="default"in n?n.default:n;function o(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function l(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function a(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}i=i&&i.hasOwnProperty("default")?i.default:i;function s(){}var u=n.Fragment?function(t){return t?1<t.length?n.createElement.apply(null,[n.Fragment,null].concat(t)):e.Children.only(t):null}:e.Children.only,c={};if("production"!==process.env.NODE_ENV){var p=i.oneOfType([i.object,i.string]).isRequired,f=function(r,o){return function(e){if(!r.split(" ").some(function(t){return e[t]})){for(var t=arguments.length,n=new Array(1<t?t-1:0),i=1;i<t;i++)n[i-1]=arguments[i];return o.apply(void 0,[e].concat(n))}}};c={I:f("do",i.string.isRequired),a:f("on this of an",p),an:f("on this of a",p),of:f("on a this an",p),this:f("on a of an",p),do:f("I",i.string.isRequired),on:f("this a of an",p),not:i.bool,passThrough:i.bool,children:i.any.isRequired,ability:i.instanceOf(r.Ability).isRequired}}var y=function(r){function t(){for(var t,e=arguments.length,n=new Array(e),i=0;i<e;i++)n[i]=arguments[i];return(t=r.call.apply(r,[this].concat(n))||this).unsubscribeFromAbility=s,t._isAllowed=!1,t._ability=null,t}a(t,r);var e=t.prototype;return e.componentWillUnmount=function(){this.unsubscribeFromAbility()},e.connectToAbility=function(t){var e=this;t!==this._ability&&(this.unsubscribeFromAbility(),this._ability=null,t&&(this._ability=t,this.unsubscribeFromAbility=t.on("updated",function(){return e.forceUpdate()})))},e.isAllowed=function(){var t=this.props,e=(t.I||t.do).split(/\s+/),n=e[0],i=e[1],r=t.of||t.a||t.an||t.this||t.on,o=t.not?"cannot":"can";return t.ability[o](n,r,i)},e.render=function(){return this.connectToAbility(this.props.ability),this._isAllowed=this.isAllowed(),this.props.passThrough||this._isAllowed?this.renderChildren():null},e.renderChildren=function(){var t=this.props,e=t.children,n=t.ability,i="function"==typeof e?e(this._isAllowed,n):e;return u(i)},function(t,e,n){e&&o(t.prototype,e),n&&o(t,n)}(t,[{key:"allowed",get:function(){return this._isAllowed}}]),t}(n.PureComponent);l(y,"propTypes",c),t.Can=y,t.createCanBoundTo=function(t){var e,n;return n=e=function(t){function e(){return t.apply(this,arguments)||this}return a(e,t),e}(y),l(e,"propTypes",Object.assign({},y.propTypes,{ability:i.instanceOf(r.Ability)})),l(e,"defaultProps",{ability:t}),n},t.createContextualCan=function(t){return function(e){return n.createElement(t,null,function(t){return n.createElement(y,{ability:e.ability||t,I:e.I||e.do,a:e.on||e.a||e.an||e.of||e.this,not:e.not,children:e.children,passThrough:e.passThrough})})}},Object.defineProperty(t,"__esModule",{value:!0})});
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],n):n(((t=t||self).casl=t.casl||{},t.casl.react={}),t.React)}(this,(function(t,n){"use strict";var i="default"in n?n.default:n;function r(t,n){for(var i=0;i<n.length;i++){var r=n[i];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}function u(){return(u=Object.assign||function(t){for(var n=1;n<arguments.length;n++){var i=arguments[n];for(var r in i)Object.prototype.hasOwnProperty.call(i,r)&&(t[r]=i[r])}return t}).apply(this,arguments)}function e(t,n){t.prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n}var o=function(){},f=n.Fragment?function(t){return t?t.length>1?n.createElement.apply(void 0,[n.Fragment,null].concat(t)):i.Children.only(t):null}:i.Children.only,c=function(t){function n(){for(var n,i=arguments.length,r=new Array(i),u=0;u<i;u++)r[u]=arguments[u];return(n=t.call.apply(t,[this].concat(r))||this).t=!1,n.i=null,n.u=o,n}e(n,t);var i,u,c,s=n.prototype;return s.componentWillUnmount=function(){this.u()},s.o=function(t){var n=this;t!==this.i&&(this.u(),this.i=null,t&&(this.i=t,this.u=t.on("updated",(function(){return n.forceUpdate()}))))},s.s=function(){var t=this.props,n=t.of||t.a||t.an||t.this||t.on,i=t.not?"cannot":"can";return t.ability[i](t.I||t.do,n,t.field)},s.render=function(){return this.o(this.props.ability),this.t=this.s(),this.props.passThrough||this.t?this.h():null},s.h=function(){var t=this.props,n=t.children,i=t.ability,r="function"==typeof n?n(this.t,i):n;return f(r)},i=n,(u=[{key:"allowed",get:function(){return this.t}}])&&r(i.prototype,u),c&&r(i,c),n}(n.PureComponent);t.Can=c,t.createCanBoundTo=function(t){var n,i;return i=n=function(t){function n(){return t.apply(this,arguments)||this}return e(n,t),n}(c),n.defaultProps={ability:t},i},t.createContextualCan=function(t){return function(i){return n.createElement(t,null,(function(t){return n.createElement(c,u({ability:t},i))}))}},Object.defineProperty(t,"l",{value:!0})}));
//# sourceMappingURL=index.js.map

@@ -1,45 +0,1 @@

import { PureComponent, StatelessComponent } from 'react'
import { Ability } from '@casl/ability'
type BaseProps = {
do: string
on: any
} | {
I: string
a: string
} | {
I: string
an: string
} | {
I: string
of: any
} | {
I: string
this: any
};
type CanPropsStrict = BaseProps & {
ability: Ability
not?: boolean
passThrough?: boolean
}
type CanProps = BaseProps & {
ability?: Ability
not?: boolean
passThrough?: boolean
}
declare class CanComponent<T> extends PureComponent<T> {
allowed: boolean
}
export class Can extends CanComponent<CanPropsStrict> {
}
export class BoundCan extends CanComponent<CanProps> {
}
export function createCanBoundTo(ability: Ability): typeof BoundCan
export function createContextualCan(Consumer: any): StatelessComponent<CanProps>
export * from './dist/types';
{
"name": "@casl/react",
"version": "1.0.4",
"version": "2.0.0",
"description": "React component for CASL which makes it easy to add permissions in any React application",

@@ -17,11 +17,9 @@ "main": "dist/umd/index.js",

"scripts": {
"rollup": "rollup -g react:React,prop-types:React.PropTypes,@casl/ability:casl",
"build.es": "npm run rollup -- -c ../../tools/rollup.es.js",
"build.umd": "npm run rollup -- -c ../../tools/rollup.umd.js -n casl.react",
"build.es5m": "npm run rollup -- -c ../../tools/rollup.es5m.js",
"build": "npm run build.es && npm run build.umd && npm run build.es5m",
"lint": "eslint src/",
"prebuild": "npm run build.types",
"build": "rollup -c ../../tools/rollup.config.js -i src/index.ts -n casl.react -g react:React,prop-types:React.PropTypes,@casl/ability:casl",
"build.types": "rm -rf dist/types/* && tsc",
"lint": "eslint --ext .ts,.js src/ spec/",
"test": "NODE_ENV=test jest --config ../../tools/jest.config.js",
"prerelease": "npm test && NODE_ENV=production npm run build",
"release": "semantic-release -e semantic-release-monorepo -e ../../tools/semantic-release"
"release": "semantic-release -e ../../tools/semantic-release"
},

@@ -45,12 +43,14 @@ "keywords": [

"@casl/ability": "^2.0.0 || ^3.0.0",
"prop-types": "^15.0.0",
"react": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
"@casl/ability": "^2.0.0",
"check-prop-types": "^1.1.2",
"prop-types": "^15.6.0",
"@casl/ability": "^3.0.0",
"@types/react": "^16.9.19",
"react": "^16.3.0",
"react-test-renderer": "^16.3.0"
}
},
"files": [
"dist",
"*.d.ts"
]
}

@@ -1,4 +0,4 @@

# CASL React [![@casl/react NPM version](https://badge.fury.io/js/%40casl%2Freact.svg)](https://badge.fury.io/js/%40casl%2Freact) [![](https://img.shields.io/npm/dm/%40casl%2Freact.svg)](https://www.npmjs.com/package/%40casl%2Freact) [![CASL Documentation](https://img.shields.io/badge/documentation-available-brightgreen.svg)](https://stalniy.github.io/casl/) [![CASL Join the chat at https://gitter.im/stalniy-casl/casl](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/stalniy-casl/casl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# CASL React [![@casl/react NPM version](https://badge.fury.io/js/%40casl%2Freact.svg)](https://badge.fury.io/js/%40casl%2Freact) [![](https://img.shields.io/npm/dm/%40casl%2Freact.svg)](https://www.npmjs.com/package/%40casl%2Freact) [![CASL Documentation](https://img.shields.io/badge/documentation-available-brightgreen.svg)](https://stalniy.github.io/casl/) [![CASL Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/stalniy-casl/casl)
This package allows to integrate [@casl/ability][casl-ability] into [React][react] application. So, you can show or hide UI elements based on user ability to see them.
This package allows to integrate `@casl/ability` with [React] application. It provides `Can` component that allow to hide or show UI elements based on user ability to see them.

@@ -9,80 +9,93 @@ ## Installation

npm install @casl/react @casl/ability
# or
yarn add @casl/react @casl/ability
# or
pnpm add @casl/react @casl/ability
```
## Getting Started
## Can component
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](#3-property-names-and-aliases))
* `I` (`do` is an alias) - name of the action and field (e.g., `read`, `update` or `read title`).\
**Note**: action names are not allowed to have spaces (e.g., this is invalid action `send email`) because `<Can>` component expects that the 2nd word in action is a field name which needs to be checked.
* `a` (`on`, `of`, `this` are aliases) - checked subject
* `not` - checks whether the ability does *not* allow an action
* `ability` - an instance of `Ability` which will be used to check permissions
It accepts children and 6 properties:
`children` property may be either a render function (a recommended way):
* `do` - name of the action (e.g., `read`, `update`). Has an alias `I`
* `on` - checked subject. Has `a`, `an`, `this` aliases
* `field` - checked field
```jsx
<Can I="create" a="Post" ability={ability}>
{() => <button onClick={this.createPost.bind(this)}>Create Post</button>}
</Can>
```
```jsx
export default ({ post }) => <Can I="read" this={post} field="title">
Yes, you can do this! ;)
</Can>
```
or React elements:
* `not` - inverts ability check and show UI if user cannot do some action:
```jsx
<Can I="create" a="Post" ability={ability}>
<button onClick={this.createPost.bind(this)}>Create Post</button>
</Can>
```
```jsx
export default () => <Can not I="create" a="Post">
You are not allowed to create a post
</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`)
* `passThrough` - renders children in spite of what `ability.can` returns. This is useful for creating custom components based on `Can`. For example, if you need to disable button based on user permissions:
### 1. Scoping Can to use a particular ability
```jsx
export default () => (
<Can I="create" a="Post" passThrough>
{allowed => <button disabled={!allowed}>Save</button>}
</Can>
)
```
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.
* `ability` - an instance of `Ability` which will be used to check permissions
* `children` - elements to hide or render. May be either a render function:
There are 2 function which allow to scope `Can` to use a particular instance of `Ability`:
* `createCanBoundTo`
* `createContextualCan`
```jsx
export default () => <Can I="create" a="Post" ability={ability}>
{() => <button onClick={this.createPost}>Create Post</button>}
</Can>
```
The first function just creates a new component which is bound to a particular ability and accepts only 2 properties: `do` and `on`:
or React elements:
```js
// Can.js
import { createCanBoundTo } from '@casl/react'
import ability from './ability'
```jsx
export default () => <Can I="create" a="Post" ability={ability}>
<button onClick={this.createPost}>Create Post</button>
</Can>
```
export default createCanBoundTo(ability)
```
> it's better to pass children as a render function because it will not create additional React elements if user doesn't have ability to do some action (in the case above `read Post`)
Then import bound `Can` into any component (now you don't need to pass `ability` property):
Don't be scared by the amount of properties component takes, we will talk about how to bind some of them.
```jsx
import Can from './Can'
### Bind Can to a particular Ability instance
export function button() {
return (
<Can I="create" a="Post">
{() => <button onClick={this.createPost.bind(this)}>Create Post</button>}
</Can>
)
}
```
It'd be inconvenient to pass `ability` in every `Can` component. That's why there are 2 function which allow to bind `Can` to use a particular instance of `Ability`:
`createContextualCan` uses [React Context API][react-ctx-api] (available from React@16.3.0) and provides specified ability to all children components.
* `createCanBoundTo`\
This function was created to support version of React < 16.4.0, those versions doesn't have [Context API][react-ctx-api]. Can be used like this:
```js
// ability-context.js
import { createContext } from 'react'
import { createContextualCan } from '@casl/react'
```js @{data-filename="Can.js"}
import { createCanBoundTo } from '@casl/react';
import ability from './ability';
export const AbilityContext = createContext()
export const Can = createContextualCan(AbilityContext.Consumer)
```
export const Can = createCanBoundTo(ability);
```
* `createContextualCan`\
This function is created to support [React's Context API][react-ctx-api] and can be used like this:
Later you need to provide your ability to `AbilityContext.Provider`
```js @{data-filename="Can.js"}
import { createContext } from 'react';
import { createContextualCan } from '@casl/react';
```jsx
import { AbilityContext } from './ability-context'
export const AbilityContext = createContext();
export const Can = createContextualCan(AbilityContext.Consumer);
```
The 2 methods are almost the same, the 2nd one is slightly better because it will allow you to provide different `Ability` instances to different parts of your app and inject ability using [`contextType` static property](https://reactjs.org/docs/context.html#classcontexttype). Choose your way based on the version of React you use.
> In this guide, we will use `createContextualCan` as it covers more cases in modern React development.
To finalize things, we need to provide an instance of `Ability` via `AbilityContext.Provider`:
```jsx @{data-filename="App.jsx"}
import { AbilityContext } from './Can'
import ability from './ability'

@@ -99,12 +112,14 @@

And inside `TodoApp` you can use previously created `Can` component:
> See [CASL guide](../../guide/intro) to learn how to define `Ability` instance.
and use our `Can` component:
```jsx
import React, { Component } from 'react'
import { Can } from './ability-context'
import { Can } from './Can'
export class TodoApp extends Component {
createTodo() {
// ....
}
createTodo = () => {
// implement logic to show new todo form
};

@@ -114,3 +129,3 @@ render() {

<Can I="create" a="Todo">
{() => <button onClick={this.createTodo.bind(this)}>Create Todo</button>}
<button onClick={this.createTodo}>Create Todo</button>
</Can>

@@ -122,155 +137,156 @@ )

Alternatively you may use [React's `contextType` component property](https://reactjs.org/docs/context.html#classcontexttype) to set context for the component:
Sometimes the logic in a component may be a bit complicated, so you can't use `<Can>` component. In such cases, you can use [React's `contextType` component property](https://reactjs.org/docs/context.html#classcontexttype):
```jsx
class MyComponent extends PureComponent {
// ...
import React, { Component } from 'react'
import { AbilityContext } from './Can'
export class TodoApp extends Component {
createTodo = () => {
// logic to show new todo form
};
render() {
// this.context is a provided Ability instance
return this.context.can('manage', 'Todo') ? <TodoApp /> : null
return (
<div>
{this.context.can('create', 'Todo') &&
<button onClick={this.createTodo}>Create Todo</button>}
</div>
);
}
}
MyComponent.contextType = AbilityContext
````
TodoApp.contextType = AbilityContext;
```
See [casl-react-example][casl-react-example] for more examples.
or `useContext` hook:
### 2. Defining Abilities
```jsx
import React from 'react';
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
export default () => {
const createTodo = () => { /* logic to show new todo form */ };
const ability = useContext(AbilityContext);
To define empty ability:
return (
<div>
{ability.can('create', 'Todo') &&
<button onClick={createTodo}>Create Todo</button>}
</div>
);
}
```js
// ability.js
import { Ability } from '@casl/ability'
export default new Ability([])
```
To create ability using `AbilityBuilder`
In this case, you need to create a new `Ability` instance when you want to update user permissions (don't use `update`, it won't trigger re-rendering in this case) or you need to force re-render the whole app.
```js
// ability.js
import { AbilityBuilder } from '@casl/ability'
### Usage note on React < 16.4 with TypeScript
export default AbilityBuilder.define(can => {
can('read', 'all')
// ....
})
```
If you use TypeScript and React < 16.4 make sure to add `@casl/react/contextAPIPatch.d.ts` file in your `tscofig.json`, otherwise your app won't compile:
Later in your login component:
```jsx
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>
)
}
```json
{
// other configuration options
"include": [
"src/**/*",
"@casl/react/contextAPIPatch.d.ts" // <-- add this line
]
}
```
Obviously, in this case your server API should provide the list of user abilities in `rules` field of the response.
See [@casl/ability][casl-ability] package for more information on how to define abilities.
### Property names and aliases
### 3. Property names and aliases
As you can see from the code above, component name and its property names and values create an English sentence, actually a question. For example, the code below reads as `Can I create a Post`:
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`:
```jsx
<Can I="create" a="Post">
{() => <button onClick={...}>Create Post</button>}
export default () => <Can I="create" a="Post">
<button onClick={...}>Create Post</button>
</Can>
```
There are several other property aliases which allow to construct a readable question (all possible combinations of readable alias names can be found in [Typescript definitions](./index.d.ts#L4):
There are several other property aliases which allow to construct a readable question:
* use `a` (or `an`) alias when you check by Type
```jsx
<Can I="read" a="Post">...</Can>
```
```jsx
export default () => <Can I="read" a="Post">...</Can>
```
* use `of` alias instead of `a` when you check by subject field
* use `this` alias instead of `a` when you check action on a particular instance. So, the question can be read as "Can I read this *particular* post?"
```jsx
<Can I="read title" of="Post">...</Can>
```jsx
// `this.props.post` is an instance of `Post` class (i.e., model instance)
export default () => <Can I="read" this={this.props.post}>...</Can>
```
// or when checking on instance. `this.props.post` is an instance of `Post` class (i.e., model instance)
* use `do` and `on` if you are bored and don't want to make your code more readable ;)
<Can I="read title" of={this.props.post}>...</Can>
```
```jsx
// `this.props.post` is an instance of `Post` class (i.e., model instance)
export default () => <Can do="read" on={this.props.post}>...</Can>
* use `this` alias instead of `of` and `a` when you check action on instance
// or per field check
export default () => <Can do="read" on={this.props.post} field="title">...</Can>
```
```jsx
// `this.props.post` is an instance of `Post` class (i.e., model instance)
## TypeScript support
<Can I="read" this={this.props.post}>...</Can>
```
The package is written in TypeScript, so don't worry that you need to keep all the properties and aliases in mind. If you use TypeScript, your IDE will suggest you the correct usage and TypeScript will warn you if you make a mistake.
* use `do` and `on` if you are bored and don't want to make your code more readable :)
## Update Ability instance
```jsx
// `this.props.post` is an instance of `Post` class (i.e., model instance)
Majority of applications that need permission checking support have something like `AuthService` or `LoginService` or `Session` service (name it as you wish) which is responsible for user login/logout functionality. Whenever user login (and logout), we need to update `Ability` instance with new rules. Usually you will do this in your `LoginComponent`.
<Can do="read" on={this.props.post}>...</Can>
Let's imagine that server returns user with a role on login:
// or per field check
<Can do="read title" on={this.props.post}>...</Can>
```
```ts @{data-filename="Login.jsx"}
import { AbilityBuilder } from '@casl/ability';
import React, { useState, useContext } from 'react';
import { AbilityContext } from './Can';
* use `not` when you want to invert the render method
function updateAbility(ability, user) {
const { can, rules } = new AbilityBuilder();
```jsx
<Can not I="read" a="Post">...</Can>
```
if (user.role === 'admin') {
can('manage', 'all');
} else {
can('read', 'all');
}
* use `passThrough` if you want to customize behavior of `<Can>` component, for example disable button instead of hiding it:
ability.update(rules);
}
```jsx
<Can I="read" a="Post" passThrough>
{can => <button disabled={!can}>Save</button>}
</Can>
export default () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const ability = useContext(AbilityContext);
const login = () => {
const params = {
method: 'POST',
body: JSON.stringify({ username, password })
};
return fetch('path/to/api/login', params)
.then(response => response.json())
.then(({ user }) => updateAbility(ability, user));
};
return (
<form>
{/* input fields */}
<button onClick={login}>Login</button>
</form>
);
};
```
### 4. Usage with React hooks
> See [Define rules](../../guide/define-rules) to get more information of how to define `Ability`
Sometimes logic in the component may be a bit complicated, so you can't use `<Can>` component.
In such cases, you can use new React `useContext` hook:
## Want to help?
```jsx
function MyComponent() {
const ability = useContext(AbilityContext)
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for [contributing].
return ability.can('manage', 'Todo') ? <TodoApp /> : null
}
If you'd like to help us sustain our community and project, consider [to become a financial contributor on Open Collective](https://opencollective.com/casljs/contribute)
````
> See [Support CASL](../../support) for details
## Want to help?
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for [contributing][contributing]
## License

@@ -280,6 +296,4 @@

[contributing]: /CONTRIBUTING.md
[react]: https://reactjs.org/
[casl-react-example]: https://github.com/stalniy/casl-react-example
[react-ctx-api]: https://medium.com/dailyjs/reacts-%EF%B8%8F-new-context-api-70c9fe01596b
[casl-ability]: https://www.npmjs.com/package/@casl/ability
[contributing]: https://github.com/stalniy/casl/blob/master/CONTRIBUTING.md
[React]: https://reactjs.org/
[react-ctx-api]: https://reactjs.org/docs/context.html
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc