2/26/2017

React Decorator Example - Button Permission

In our application, every button's visibility is determined by the the user's role. For example, if the user's role is guest, they have no permission to create a project.
We get the permission list via ajax to decide whether each buttons is displayed or not. The traditional way to archive it is that, in render function, determine whether the button is displayed according to the user's permission.
class Page1 extend React.Component {
 render() {
  const {create, update} = window.globals.permissions;
  return (
   {create && <Button>Create Project</Button>}
   {update && <Button>Update Project</Button>}
  );
  
 }
}
It works fine, but, if there's another page called Page2 which contains some buttons like Page1. We have to write the similar code in the Page2 again. Furthermore, if the requirement changed: "Disable the button if user have no permission". Then we have modify all pages like this:
class Page1 extend React.Component {
 render() {
  const {create, update} = window.globals.permissions;
  return (
   {<Button disabled={!create} >Create Project</Button>}
   {<Button disabled={!update} >Update Project</Button>}
  );
  
 }
}
Here comes the Decorator Pattern. Since button's visibility or disability is only determined by the permission key, could we just set a property in the Button component to check it? The permission check, visibility or disability property is done by decorator.
class Page1 extend React.Component {
 render() {
  return (
   {<Button auth="create">Create Project</Button>}
   {<Button auth="update">Update Project</Button>}
  );  
 }
}
Here is the decorator:
export default function injectAuth(component) {
    const checkAuth = (props) => {
      let {auth} = props;
      // No Auth required
      if(auth == null) {
        return true;
      }
      return windows.globals.permissions[auth];      
    }

    let WrappedClass = class extends component {

      render() {
        if(checkAuth(this.props)) {
          return super.render();
        }
        else {
          const noAuthType = this.props.noAuthType || 'hidden';
          if(noAuthType === 'hidden') {
            return null;
          } else if(noAuthType === 'disabled') {
            // React doesn't allow to modify props like this.prpos.disabled=ture
            // Should clone the element to with inital props
            // See: http://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children
            return React.cloneElement(super.render(), {disabled:true});
          } else {
             throw new Error('noAuthType必须是hidden或disabled');
          }

        }
      }
    }

    return WrappedClass;
}
injectAuth(component) is a function with parameter component class which returns a new wrapped class. The wrapped class extends the parameter component so that all the properties and method is inherited. In the wrapped class's render function, it check if the auth property exists in windows.globals.permissions. If yes, just returns the component's render result.
return super.render();
If no, return null or disabled component according to the noAuthType props.
Finally, inject your button component like this:
class Button { 
 ...
}
export default injectAuth(Button);