Hide some React component children depending on user role - reactjs

I am writing a single page application in React and Redux (with a Node.js backend).
I want to implement role-based access control and want to control the display of certain parts (or sub parts) of the app.
I'm going to get permissions list from Node.js, which is just an object with such structure:
{
users: 'read',
models: 'write',
...
dictionaries: 'none',
}
key is protected resource,
value is user permission for this resource (one of: none, read, write).
I'm storing it into redux state. Seems easy enough.
none permission will be checked by react-router routes onEnter/onChange hooks or redux-auth-wrapper. It seems easy too.
But what is the best way to apply read/write permissions to any component view (e.g. hide edit button in Models component if the user has { models: 'read' } permission).
I've found this solution and change it a bit for my task:
class Check extends React.Component {
static propTypes = {
resource: React.PropTypes.string.isRequired,
permission: React.PropTypes.oneOf(['read', 'write']),
userPermissions: React.PropTypes.object,
};
// Checks that user permission for resource is the same or greater than required
allowed() {
const permissions = ['read', 'write'];
const { permission, userPermissions } = this.props;
const userPermission = userPermissions[resource] || 'none';
return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
}
render() {
if (this.allowed()) return { this.props.children };
}
}
export default connect(userPermissionsSelector)(Check)
where userPermissionsSelector would be something like this: (store) => store.userPermisisons and returns user permission object.
Then wrap protected element with Check:
<Check resource="models" permission="write">
<Button>Edit model</Button>
</Check>
so if user doesn't have write permission for models the button will not be displayed.
Has anyone done anything like this? Is there more "elegant" solution than this?
thanks!
P.S. Of course user permission will also be checked on the server side too.

Well I think I understood what you want. I have done something that works for me and I like the way I have it but I understand that other viable solutions are out there.
What I wrote was an HOC react-router style.
Basically I have my PermissionsProvider where I init the users permissions. I have another withPermissions HOC that injects the permissions I provided earlier into my component.
So if I ever need to check permissions in that specific component I can access them easily.
// PermissionsProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
class PermissionsProvider extends React.Component {
static propTypes = {
permissions: PropTypes.array.isRequired,
};
static contextTypes = {
permissions: PropTypes.array,
};
static childContextTypes = {
permissions: PropTypes.array.isRequired,
};
getChildContext() {
// maybe you want to transform the permissions somehow
// maybe run them through some helpers. situational stuff
// otherwise just return the object with the props.permissions
// const permissions = doSomething(this.props.permissions);
// maybe add some validation methods
return { permissions: this.props.permissions };
}
render() {
return React.Children.only(this.props.children);
}
}
const withPermissions = Component => {
const C = (props, context) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} />
);
};
C.displayName = `withPermissions(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
C.contextTypes = {
permissions: PropTypes.array.isRequired
};
return hoistStatics(C, Component);
};
export { PermissionsProvider as default, withPermissions };
Ok I know this is a lot of code. But these are HOC (you can learn more here).
A higher-order component (HOC) is an advanced technique in React for
reusing component logic. HOCs are not part of the React API, per se.
They are a pattern that emerges from React’s compositional nature.
Concretely, a higher-order component is a function that takes a
component and returns a new component.
Basically I did this because I was inspired by what react-router did.
Whenever you want to know some routing stuff you can just add the decorator #withRouter and they inject props into your component. So why not do the same thing?
First you must setup the permissions with the provider
Then you use the withPermissions decorator on the components that check permissions
//App render
return (
<PermissionsProvider permissions={permissions}>
<SomeStuff />
</PermissionsProvider>
);
Somewhere inside SomeStuff you have a widely spread Toolbar that checks permissions?
#withPermissions
export default class Toolbar extends React.Component {
render() {
const { permissions } = this.props;
return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />;
}
}
If you can't use decorators you export the Toolbar like this
export default withPermissions(Toolbar);
Here is a codesandbox where I showed it in practice:
https://codesandbox.io/s/lxor8v3pkz
NOTES:
I really really simplified the permissions because that logic comes from your end and for demo purposes I simplified them.
I assumed the permissions were an array so that is why I check the PropTypes.array in the HOCs
It's a really long and complicated answer and I tried to articulate at my best ability. Please don't grill me for some mistakes here and there :)

The approach that suggested by #lokuzt is great.
And you can go even further in order to simplify your code.
First of all, every protected component has some requirement to be satisfied for render. You need to define a function that takes requirement to render and credentials of the current user as parameters. It must return true or false.
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
Further, we have to define a HOC (Higher-Order Component) using the new context API from ReactJS.
const { Provider, Consumer } = React.createContext();
function protect(requirement, WrappedComponent) {
return class extends Component {
render() {
return (
<Consumer>
{ credentials => isSatisfied(requirement, credentials)
? <WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>
: null
}
</Consumer>
);
}
};
}
Now you can decorate your components:
const requireAdmin = {...}; // <- this is your requirement
class AdminPanel extends Component {
...
}
export default protect(requireAdmin, AdminPanel);
or even third-party components:
import {Button} from 'react-bootstrap';
const AdminButton = protect(requireAdmin, Button);
Credentials have to be passed by ReactJS context API:
class MyApp extends Component {
render() {
const {credentials} = this.props;
<Provider value={credentials}>
...
<AdminPanel/>
<AdminButton>
Drop Database
</AdminButton>
...
</Provider>
}
}
Here is my extended implementation on github.
The demo is also available too.

Related

How to pass state to React JS High Order Component

I am using OIDC redux connector for user state. I have a few components that require authentication. I would like to use something like export default connect(mapStateToProps, mapDispatchToProps)(withAuth(Component)); and request data from state inside my authentication service.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
export const withAuth = (Component) => {
return props => {
return <Component {...props} />
}
}
Is it possible to get state in the render function? So I can check the user beinig logged in and redirect to the sign-in page if there is no user signed in?
BTW: How would I redirect? I have tried using redirect-router-dom and <Redirect /> But then it complains about set state being changed too often ... But that might be my mistake. I get this error when I render a Redirect: Error: Maximum update depth exceeded.
If I understand correctly you want to "decorate" your component with additional logic to handle an authentication redirect?
I suggest using a "decorator" pattern here e.g.:
export const withAuth = (Component) => {
const mapStateToProps = (state, ownProps) => ({
authenticated: state.authenticated // Or what you need to do to determine this
});
return connect(mapStateToProps)(class extends React.Component {
render() {
const { authenticated, ...componentProps } = props;
if (authenticated) {
return <Component {...componentProps }>;
}
return <Redirect to="/login" />;
}
});
}
Then when you need this you can do things like:
export default withAuth(connect(yourOwnMapStateToProps)(YourComponent))
Just figured it out, I changed the store so instead of returning a function, it returns the object. So I can load in all js files. It might not be the best solution. If there is a better way to get the store in code, I would love to hear about how to do that. The configurestore function is what I found in quite a lot of examples.
import { store } from '../configureStore';
Using store.getState() I can get the current state.
The redirect issue I am having is similar to: How to use react-transition-group with react-router-dom

What is the best approach to render multiple React componenes

I wanna to create a following React/Redux app, and I'm looking for the best approach to handle this.
Let's start from main App container:
class App extends React.Component {
render() {
const {open} = this.props;
return open ? <Maximized/> : <Minimized/>;
}
}
const mapStateToProps = (state) => ({
open: getIsOpen(state)
});
export default connect(
mapStateToProps,
{}
)(App);
Now it's easy - depends on the state I render only two containers. Minimized container is easy so let's skip it.
In Maximized container I want to display a different views(?).
class Maximized extends React.Component {
render() {
const {view} = this.props;
let content = null;
if (view === 'DEFAULT') {
content = <Component1/>
} else if (view === 'COMPONENT2') {
content = <Component2/>
}
return <div>
{content}
<Close/>
</div>;
};
}
const mapStateToProps = (state) => ({
view: getView(state)
});
export default connect(
mapStateToProps,
{}
)(Maximized);
view is from state selector getView and is handled by the simple if, but now I have only two components to display, in the future I think this components can be n.
I think about router with the MemoryRouter (I can't use URL), but I don't know is a good approach because of I must store current state in the local storage.
Maybe there is any pattern, good practice or tool for archive this.
I have used a similar pattern a lot and I think it works well. One change is I would just clean up your conditional logic a bit. I would use a switch instead so that it's much easier and cleaner to handle n amount of components:
class Maximized extends React.Component {
getView = () => {
const {view} = this.props;
switch(view) {
case 'DEFAULT': {
return <Component1/>
}
case 'COMPONENT2': {
return <Component2/>
}
case 'COMPONENT3': {
return <Component3/>
}
default: {
return <Component1 />
}
}
};
render() {
const content = this.getView();
return <div>
{content}
<Close/>
</div>;
};
}
const mapStateToProps = (state) => ({
view: getView(state)
});
export default connect(
mapStateToProps,
{}
)(Maximized);
Well, it is kinda strange that you would have so many components using the same URL. With that being said, if you absolutely can't use URL, I guess you can create a file that import all your components, and export an object acting as a map between your view value and your component class. Something like:
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';
export default {
'DEFAULT': ComponentA,
'PROFILE': ComponentB,
'CONTACT': ComponentC,
}
Then at your App container, you can probably do something like this:
import ComponentMap from './ComponentMap';
...
...
...
render() {
const Component = ComponentMap[this.state.view];
return <Component />;
}
codesandbox for demo
EDIT:
The reason for putting all the mapping to components at separate file is to have a separation of concern. Using a traditional switch or if logic would require you to add at the very least 2 lines of code to your App container. In time, your App container could be very lengthy. If your App container also handle other things (not just mapping to components), then it would be not very pleasant to work with it.
By using separate file, your App container will not be filled with all the mappings concern. The separate file is also less verbose than using switch or if. Compare adding
'KEY': ComponentX,
to
case 'KEY': ComponentX; break;
Last but not least, using switch or if would result in O(n) time to get the component you are looking for, compared to O(1) time if you are using javascript object.

Meteor loading data with React Komposer

I'm trying to load data using React Komposer and I'm not sure what I'm doing wrong, pretty sure this is the way it should be unless I miss something. But I'm not getting any data in the UI. Could use the help
container.js
import { composeWithTracker } from 'react-komposer';
import RightNavBar from './right-nav-bar.jsx';
function composer(props, onData) {
const subscription = Meteor.subscribe('currentUser');
const currentUser = 'bbbb';
onData(null, currentUser);
}
export default composeWithTracker(composer)(RightNavBar);
My component
export class RightNavBar extends React.Component {
render() {
return (
<div>
aaaa {currentUser}
</div>
);
}
}
Here is the "standard" example from react-komposer's repository (adapted to your specific case)
function composer(props, onData) {
const subscription = Meteor.subscribe('currentUser');
if (subscription.ready()) {
const currentUser = Meteor.user(); //or whatever
onData(null, {currentUser});
};
};
Here you subscribe and when the subscription is ready, your component is rendered. Otherwise, a loading component is rendered.
The 2nd parameter to onData should be an object. It is merged with other props passed to your component and is accessible from within your component via this.props.
From within your component,the props object is available via this.props, so you can either deconstruct it or access its properties directly.
class RightNavBar extends React.Component {
render() {
const {currentUser} = this.props;
return (
<div>
Hello, {currentUser.name}!
</div>
);
}
}
Your code sends a string rather than an object and React has no way of making sense of the token currentUser from within your component.

Reusing container and container extending other containers

import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { setLocation } from 'redux/modules/filters'
import SearchForm from 'components/SearchForm/SearchForm'
type Props = {
};
export class HomeSearchContainer extends React.Component {
props: Props;
static contextTypes = {
router: React.PropTypes.object.isRequired
};
constructor(props) {
super(props)
this.onSearch = this.onSearch.bind(this)
}
onSearch(address, event) {
event.preventDefault()
if (address) {
this.props.actions.setLocation(address)
}
this.context.router.push('/browse_items')
}
render() {
return (
<SearchForm
onSearch={this.onSearch}
currentLocation={this.props.currentLocation}
/>
)
}
}
const mapStateToProps = (state) => {
return {
currentLocation: state.filters.location
}
}
const mapDispatchToProps = (dispatch) => {
var actions = {
setLocation
}
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeSearchContainer)
I have a few questions to validate my understanding.
Do we ever re-use containers? Am I correct if I say we intend to re-use components but not containers?
In the above code, I want to create another container that doesn't redirect to /browse_items. In other words, I just want to override onSearch function of this container. Is it okay to extend this container?
First of all, in my mind a container is a certain kind of component, so following this article: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.jqwffnfup I'll rather talk about container components and presentational components.
Ad 1) I would say we can re-use container components as well as presentational components - this always depends on how our code is structured. E.g. a container component might have child components, which can be different in different parts of the screen, but the container component is being re-used.
Ad 2) If you just want to have a different onSearch functionality, I might consider passing the onSearch callback to the HomeSearchContainer via the props. That way, you can re-use the same container, but it behaves differently.
Looking closely at your code, there is then not much left to the HomeSearchContainer, so you might as well use the SearchForm directly. If you need the SearchForm in multiple places, it might be worth pulling out the onSearch function into its own file and re-using that. But this is hard to judge without seeing the rest of the application. So I'm trying to throw some ideas at you here, which may or may not apply to your codebase.

React-Redux - Reuseable Container/Connector

I am completely lost on the react-redux container (ie connector) concept as it is not doing what I anticipated. My issue is straight forward, and to me reasonable, yet I cannot find a well written example of how to accomplish it.
Let us say we have a react component that connects to a store that has product context, and we will call this component ProductContext.
Furthermore, let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions on every other component that may need products.
Illustratively this is what I mean:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
from SeasonalProducts:
<ProductContext >
// do something with props from container
</ProductContext >
From the examples I see at react-redux, it appears to me that their containers lump both seasonal and discontinued products in the container itself. How is that reusable?
from the ProductContextComponent:
<section >
<DiscontinuedProducts />
<SeasonalProducts />
</section >
Complicating matters, while trying to keep a cool head about this most frustrating matter, "nebulous tersity" seems to be the only responses I receive.
So here is my ProductContext:
#connect(state => ({
products: state.productsReducer.products
}))
export default class ProductContext extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
// get from parent
static contextTypes = {
clientInfo: PropTypes.object.isRequired
};
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
render() {
if (!this.props.products) return <Plugins.Loading />;
.....
return (
// I want to put the products array here
);
}
};
Then my thinking is if I do this:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued.
Am I completely wrong on this? Is this not reasonable to desire?
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me. I have spent over a week on this issue and am about ready to give up on react-redux.
UPDATE: A very slick solution below with use of a HOC.
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me.
Look at the Shopping Cart example.
Examples code from: shopping-cart/containers/CartContainer.js
Let us say we have a react component that connects to a store that has product context,
There isn't a store for products and a store for users, etc. There is one store for everything. You should use reducers to take the full store and reduce to what your component needs.
From the example:
import { getTotal, getCartProducts } from '../reducers'
const mapStateToProps = (state) => {
return {
products: getCartProducts(state),
total: getTotal(state)
}
}
Here state is the entire store.
let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions
Components don't dispatch actions, they call functions passed to them. The functions live in an actions module, which you import and pass to container as props. In turn, the container passes those functions to component props.
From the example:
import { checkout } from '../actions'
CartContainer.propTypes = {
checkout: PropTypes.func.isRequired
}
connect(mapStateToProps,
{ checkout }
)(CartContainer)
What connect does, it subscribes to store changes, calls the map function, merges with constant props (here the action function), and assigns new props to the container.
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued
This is actually spot on. The knowledge you mention is the one store, and it should absolutely filter in a reducer.
Hopefully this clears things up.
I have found a more practical way of utilizing repetitive data from redux than the docs. It makes no sense to me to repeat mapToProps and dispatch instantiation on every blessed component when it was already done once at a higher level, and there inlies the solution. Make sure your app is Babel 6 compliant as I used decorators.
1. I created a higher order component for the context ....
product-context.js:
import React, { Component, PropTypes } from 'react';
// redux
import { connect } from 'react-redux';
import { fetchProductsIfNeeded } from '../../redux/actions/products-actions';
// productsReducer is already in state from index.js w/configureStore
#connect(state => ({
products: state.productsReducer.products
}))
export default function ProductContext(Comp) {
return (
class extends Component {
static propTypes = {
children: PropTypes.node,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
static contextTypes = {
clientInfo: PropTypes.object.isRequired;
};
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
render() {
if (!this.props.products) return (<div>Loading products ..</div>);
return (
<Comp products={ this.props.products }>
{ this.props.children }
</Comp>
)
}
}
)
}
2. component utilizing product-context.js
carousel-slider.js:
import React, { Component, PropTypes } from 'react';
......
import ProductContext from '../../../context/product-context';
#Radium
#ProductContext
export default class CarouselSlider extends Component {
constructor(props) {
super(props); }
......
static showSlideShow(carouselSlides) {
carouselSlides.map((slide, index) => {
......
results.push (
......
)
});
return results;
}
render() {
const carouselSlides = this.props.products;
const results = CarouselSlider.showSlideShow(carouselSlides);
return (
<div id="Carousel" className="animation" ref="Carousel">
{ results }
</div>
);
}
}
So there you go. All I needed was a decorator reference to product-context, and as a HOC, it returns the carousel component back with the products prop.
I saved myself at least 10 lines of repetitive code and I removed all related contextType from lower components as it is no longer needed with use of the decorator.
Hope this real world example helps as I detest todo examples.

Resources