React components to build permission controlled ui's.
With inspiration from Pundit.
This lib returns arrays of elements in render so requires react >= 16. If you are using a lower version of react use version 1.x.x of this lib.
Install it via npm:
npm install --save react-punditPundit based routes react-router Pundit.
import { PunditContainer, VisibleIf } from 'react-pundit';
import policies from './policies.js';
import './App.css';
class App extends Component {
render() {
const userOne = { id: 1, role: 'basic', activated: false };
const userTwo = { id: 2, role: 'basic', activated: true };
const userAdmin = { id: 3, role: 'admin', activated: true };
const userOneActivated = { id: 1, role: 'basic', activated: true };
const post = { user: { id: 1 }, body: 'test', editable: true };
return (
<divclassName="App"><PunditContainerpolicies={policies}user={userOne}><PunditTypeSettype="Post"><VisibleIfaction="Create"><button>create will not show</button></VisibleIf><VisibleIfaction="Create" user={userTwo}><button>create will show</button></VisibleIf><VisibleIfaction="Create" user={userAdmin}><button>create will show</button></VisibleIf><VisibleIfaction="Edit" model={post}><button>edit will not show</button></VisibleIf><VisibleIfaction="Edit" model={post}user={userOneActivated}><button>edit will show</button></VisibleIf><VisibleIfaction="Edit" model={post}user={userAdmin}><button>edit will show</button></VisibleIf><VisibleIftype="Comment" action="Create" user={userOneActivated}><button>comment create will show</button></VisibleIf></PunditTypeSet></PunditContainer></div>
);
}
}// policies.js// Simple exampleexportdefault{Post: (action,model,user)=>{if(user.activated===false){returnfalse;}if(user.role==='admin'){returntrue;}switch(action){case'Create':
returntrue;case'Edit':
return(model.editable&&user.id===model.user.id);default:
returnfalse;}},Comment: (action,model,user)=>{if(user.activated===false){returnfalse;}if(user.role==='admin'){returntrue;}switch(action){case'Create':
returntrue;default:
returnfalse;}}};// Function based exampleimport{createPolicy,toPolicyObject}from'react-pundit';constPostPolicy=createPolicy('Post');PostPolicy.addAction('Edit',(model,user)=>{returnuser.activated&&(user.role==='admin'||(model.editable&&user.id===model.user.id));});PostPolicy.addAction('Create',(model,user)=>{returnuser.activated;});constCommentPolicy=createPolicy('Comment');CommentPolicy.addAction('Create',(model,user)=>{returnuser.activated;});exportdefaulttoPolicyObject([PostPolicy,CommentPolicy]);// OO exampleimport{PunditPolicy,toPolicyObject}from'react-pundit';classPostPolicyextendsPunditPolicy{constructor(){super('Post');}Edit(model,user){returnuser.activated&&(user.role==='admin'||(model.editable&&user.id===model.user.id));}Create(model,user){returnuser.activated;}}classCommentPolicyextendsPunditPolicy{constructor(){super('Comment');}Create(model,user){returnuser.activated;}}exportdefaulttoPolicyObject([newPostPolicy(),newCommentPolicy()]);// Available componentsimport{PunditContainer,PunditTypeSet,VisibleIf,IfElseButton}from'react-pundit';// Available helpersimport{PunditPolicy,createPolicy,toPolicyObject,PunditComponent}from'react-pundit';PunditContainer is the root of react-pundit and is where the policies are set.
You can pass a user into the container and have that act as the default user for
all children that use pundit. The container will only create DOM if there is
more then one child inside it. It creates a 'div' by default in that case but you
can override with a element prop ie: element="span" or element={Wrapper}.
<PunditContainerpolicies={policies}user={optionalDefaultUser}><divclassName="App"></div></PunditContainer>PunditTypeSet is a convenience tool. It allows you not have to set the type
prop on any children in side of it as well as the model. Those children that do have type set will
override this type, the same is true for model. The type set will only create DOM if there is
more then one child inside it. It creates a 'span' by default in that case but you
can override with a element prop ie: element="div" or element={Wrapper}.
<PunditTypeSettype="DefaultType" model={optionalDefaultModel}></PunditTypeSet>VisibleIf is the base logic unit in react-pundit currently. It takes a
number of props.
type: The policy classaction || method: The method to check againstuser: The user whose permission are being checkedmodel: If needed the model the permissions are being checked against
It works so that if the permissions are met then the child will be rendered else it will not be
IfElseButton is a button that has two click handlers one ifClick that will trigger
if the user has permission and a elseClick if they do not. The button will always have
the class IfElseButton but you can add classes via the className prop.
All Props:
type: The policy classaction || method: The method to check againstuser: The user whose permission are being checkedmodel: If needed the model the permissions are being checked againstifClick: Function triggered if the user has permission and has clicked the buttonelseClick: Function triggered if the user does not have permission and has clicked the buttonclassName: Extra custom class to add to the button elementelement: Optional component to use to override the default'button'element
Any other props passed in will be passed to the rendering element.
Example:
In this case the user has to be logged in and activated to do the action but the button is
on a public facing page. We also use a custom Button component to handle the render and a prop that will be passed to it.
<IfElseButtontype="Post"
action="ToggleLike"
model={post}ifClick={() => this.toggleLike(post)}
elseClick={() => this.hasUser ? this.openModal('Please activate your account.') : this.openLogin)}
element={Button}
propSpecificToTheButton="Some Value"
>
{count} Likes
</IfElseButton>classPostPolicyextendsPunditPolicy{constructor(){super('Post');}ToggleLike(model,user){returnuser!==null&&user.activated;}
...
}PunditComponent is a base react component that can be extended to create
child components that use pundits checks. It does this by haveing all the default
params needed to run the checks and exposing passesPermissions which return a
boolean true of false for if the user has the permissions required.
Look at the source for VisibleIf for reference. This is a bit cleaner not
handling the case of more than one child.
classVisibleIfextendsPunditComponent{staticdisplayName='VisibleIf';render(){if(this.passesPermissions()){returnthis.props.children;}returnnull;}}If you need to extended the prop types or default props its is easy.
staticpropTypes={
...PunditComponent.propTypes,newProp: PropTypes.any,};staticdefaultProps={
...PunditComponent.defaultProps,newProp: 'some default',};Work in progress
See examples folder.
You can test changes by importing the library directly from a folder:
- Do changes to the library
- On your test project:
npm install /path/to/your/react-pundit/ --save - For easy development, you can
npm link react-punditon your application - And finally
npm run compilethe react-pundit to have the changes in your application
MIT