Designing and structuring a multirole ReactJS application - javascript

I am bulding a web application that talks with some APIs in reactJS.
My app has 4 roles, admin, master, slave, baby.
Every role can see something more than the others, so the the menus, and the view are slightly different between them.
e.g.
The navbar has:
for the admin: 5 buttons
for the master: 4 buttons
for the salve: 3 buttons
for the baby: 2 buttons
I wanted to know what is the best practice for structuring this application:
Should I have 4 differents apps? This way the component would be clear, but everytime that I need to implement a new function I have to modify 4 apps.
If this is the only way, how can I call the right app based on the role after the login?

Having four separate apps with a lot of overlapping code/functionality would surely not be the best approach; that would result in a lot of unnecessary repetition.
If the different roles just add/remove small features and functionality, I would just wrap rendering of these elements in conditionals. For example:
renderAdminButton: function() {
( if this.props.user.role === 'admin' ) {
return (
<button />
);
}
}
This function would then be called inside the parent app/component's render function.
However, if you REALLY want to have a separate app for each role, you could conditionally render the app with a function similar to this:
renderApp: function() {
var apps = {
admin: AdminApp,
master: MasterApp,
slave: SlaveApp,
baby: BabyApp
};
return apps[this.props.user.role];
}

In your button component check for this.props.hide and return null.
render() {
if (this.props.hide) {return null;}
return (
<button>
{this.props.btn.text}
</button>
);
}
In your parent component include logic for each buttons hide prop.
render() {
var hideAdminBtn = this.props.appState.userRole != 'admin';
return (
<JButton hide={hideAdminBtn} />
);
}

What you'll want to do is make each access type as a prop so you can render the application based on the type of prop received, ideally at componentDidMount(). You can then have a MasterView that will receive the prop and decide accordingly which view to render. The code will follow along like this:
var MasterView = React.createClass({
var roleType = this.props.role;
render: function() {
if(roleType === 'admin') {
return (
<AdminView />
);
}
});
As for your concern about implementing new features, you should break down you view and logic into simple and reusable components. For example all your access type can share a common <Header /> and <Footer /> component as well as <Profile /> <Settings /> etc. Mind you these can be further broken down so that when you make a small change in any of the components it will propagate to all your views. Similar principles will apply to your logic as well. Remember to stay DRY.
Edit:
Let's assume that the app is being made for a store. All the data displayed via this portal(indicted by the component's name) are:
<EmployeeProfile />
<EmployeeSalary />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
Now let's define the access level of each roles:
Admin has access to all the functionalities of the app.
Master can view all but the employee's salary details.
Slave has access to buyer's record, inventory and billing.
Child can only view the inventory and billing sections.
Now, these 6 broad components will be defined and will definitely comprise of multiple components. Just make sure that whenever you feel like a specific part of the view will be used elsewhere, go ahead and make it into a separate component.
Finally, the roles should render like this:
var AdminView = React.createClass({
render: function() {
return (
<div>
<EmployeeProfile />
<EmployeeSalary />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
</div>
);
}
});
var MasterView = React.createClass({
render: function() {
return (
<div>
<EmployeeProfile />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
</div>
);
}
});
var SlaveView = React.createClass({
render: function() {
return (
<div>
<BuyerRecords />
<BalanceSheet />
<Billing />
</div>
);
}
});
var ChildView = React.createClass({
render: function() {
return (
<div>
<Inventory />
<Billing />
</div>
);
}
});
This way you have 4 parent components handling all the views. They comprise of multiple components and in case a change needs to made, it needs to be done only once and it will reflect in all the roles.
If you decide to have a slightly modified <Inventory /> for each role, you can choose to render different child components of <Inventory /> as determined by the role.
Go ahead and have a look at this official blog post as well. Should help clear any doubts :)

Related

Antd Segmented: Set tabindex=-1 to the internal Input component

I'm using the Segmented component by Ant Design. My application has some component like this:
...
import EmployeeCard from './EmployeeCard';
import ContractorCard from './ContractorCard';
function MyComponent() {
const [cardType, setCardType] = useState('Employee');
...
function changeCard(value) {
setCardType(value);
}
return (
...
<Segmented
options={['Employee', 'Contractor']}
onChange={changeCard)
value={cardType}
tabIndex={-1}
/>
{cardType === 'Employee'
? <EmployeeCard />
: <ContractorCard />
}
...
);
}
The EmployeeCard and ContractorCard have some input components in them that I want to be part of the overall tab flow on the page. However, I don't want the Segmented buttons themselves to be part of it. I set tabIndex={-1} on the Segmented component in order to take it out of the tab-flow, but apparently, internally, that component includes an Input component, which still has the default tabIndex={0}. I cannot figure out how to reach that component in order to set its tab index to -1.

Is my owner react components state preventing me from updating child component state?

I'm trying to update the state of a checkbox within a modal that is mounted via button on the UI. I'm loading the settings when AppWrapper mounts so I can pass them around as needed. Right now i'm just passing the settings as props to SettingsList component, which then renders a series of child nodes as checkboxes. I'm able to click the checkboxes when the modal is open, and the settings successfully save to the database. However when the modal is closed and reopened the settings are refreshed to the initially set state from the owner. Refreshing the page though shows the accurately checked boxes. That makes sense to me, but i'm unsure they best way to resolve it.
Should I/Can I update the state of the parent from the child setting so when the modal is reopened that passed props reflect the user changes?
My react structure looks like this:
<AppWrapper>
getInitialState {settings:[]}
<Modal>
<SettingList settings={this.state.settings}>
<Setting/>
<SettingList/>
<Modal/>
<AppWrapper/>
It's not direct one to one code, bust just a representation of the hierarchy.
My Modal component looks like this:
var Modal = React.createClass({
render: function() {
if(this.props.isOpen){
return (
<ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500}>
<div className="mymodal">
{this.props.children}
</div>
</ReactCSSTransitionGroup>
);
} else {
return <ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500} />;
}
}
});
My SettingList component looks like this:
var SettingsList = React.createClass({
render: function() {
var settingNodes = this.props.settings.map(function(setting, i){
return (
<Setting data={setting}
key={i}>
</Setting>
)
}.bind(this));
return (
<div className="settings-block">
<h2>Notifications</h2>
<ul className="account-settings">
{settingNodes}
</ul>
</div>
)
}
});
And the Setting component looks like this:
var Setting = React.createClass({
saveSetting: function(one) {
core.setAccountSettings(this.refs.setting_checkbox.id, this.refs.setting_checkbox.checked).done(function(response){
this.setState({
defaultChecked: this.refs.setting_checkbox.checked
};
console.log(response)
}.bind(this));
},
render: function() {
//get values from settings object
for (var k in this.props.data) {
this.settingId = k
this.settingName = String(k.split(/_(.+)?/)[1]).replace(/_/g, " ");
this.settingValue = (this.props.data[k].toLowerCase() == "true")
}
return (
<li className="checkbox">
<input onChange={this.saveSetting} ref="setting_checkbox" id={this.settingId} className="settings_checkbox" type="checkbox" defaultChecked={this.settingValue}></input>
<label htmlFor={this.settingName}>{this.settingName}</label>
</li>
)
}
});
As pointed out in the comments above there is a number of ways to pass data between components.
http://andrewhfarmer.com/component-communication/
Following the article regarding callbacks was the solution for me.

React - passing ref to sibling

I need to have 2 sibling components, and 1 of them has to have a reference to another - is that possible?
I tried this:
<button ref="btn">ok</button>
<PopupComponent triggerButton={this.refs.btn} />
And even this
<button ref="btn">ok</button>
<PopupComponent getTriggerButton={() => this.refs.btn} />
But I get undefined in both situations. Any ideas?
Note: a parent component will not work for me, because I need to have multiple sets of those, like
<button />
<PopupComponent />
<button />
<PopupComponent />
<button />
<PopupComponent />
NOT like this:
<div>
<button />
<PopupComponent />
</div>
<div>
<button />
<PopupComponent />
</div>
<div>
<button />
<PopupComponent />
</div>
Think this question is best answered by the docs:
If you have not programmed several apps with React, your first
inclination is usually going to be to try to use refs to "make things
happen" in your app. If this is the case, take a moment and think more
critically about where state should be owned in the component
hierarchy. Often, it becomes clear that the proper place to "own" that
state is at a higher level in the hierarchy. Placing the state there
often eliminates any desire to use refs to "make things happen" –
instead, the data flow will usually accomplish your goal.
Not sure exactly what you are trying to do, but my hunch is a parent component and passing props is what you really want here.
I completely agree with the quote Mark McKelvy has provided. What you are trying to achieve is considered an anti-pattern in React.
I'll add that creating a parent component doesn't necessarily means it has to be a direct parent, you can create a parent component further up the chain, in which you can render an array of all your children components together, having the logic to coordinate between all the children (or pairs of children according to your example) sit inside your parent.
I created a rough example of the concept which should do the trick:
class A extends React.Component {
onClick(key) {
alert(this.refs[key].refs.main.innerText);
}
render() {
var children = [];
for (var i = 0; i < 5; i++)
children.push.apply(children, this.renderPair(i));
return (
<div>
{children}
</div>
);
}
renderPair(key) {
return [
<B ref={'B' + key} key={'B' + key} onClick={this.onClick.bind(this, 'C' + key)}/>,
<C ref={'C' + key} key={'C' + key} onClick={this.onClick.bind(this, 'B' + key)}/>
];
}
}
class B extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>B</p>;
}
}
class C extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>C</p>;
}
}
React.render(<A/>, document.getElementById('container'));
And any state you need to save for all your children, you do in the common parent. I really hope this helps.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls. Hope it helps.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
special-props
Special Props Warning
Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
For instance, attempting to access this.props.key from a component (eg. the render function) is not defined. If you need to access the same value within the child component, you should pass it as a different prop (ex: ). While this may seem redundant, it's important to separate app logic from reconciling hints.

React: Inject common render logic from parent

Let's assume i have a react component class that displays a modal dialog on a click of a button.
it can be created like this (in jsx):
<Modal text={"some text"}/>
Now, I have a bunch of component classes (let's call them Panels) that all have a function called getMessage, and i'd like the same behavior in all of these components: the modal dialog should show the string that returns from the call to getMessage.
the straight forward way to do this would be to include
<Modal text={this.getMessage()}/>
in the render() function for each such component.
Now, let's say that there is a bit more logic involved. for example, i would only like to render this component if getMessage is defined and does not return null.
Now this is starting to look like this:
var Panel1 = React.createClass({
getMessage: function() {return 'wow';},
render: function() {
var modal = null;
if (this.hasOwnProperty('getMessage' && this.getMessage() !== null) {
modal = <Modal text={this.getMessage()}/>
}
return (
<div>
{modal}
...all other stuff done in panel
</div>
);
}
});
This is starting to become cumbersome because I need to have this logic for each and every component class I define.
How can I achieve DRYness in this scenario so that i don't have to repeat this?
One way would be to define a utility function that contains this logic, let's call it displayModalIfNeeded and the call it from render. this now looks like this:
return (
<div>
{displayModalIfNeeded.call(this)}
....all other stuff needed in Panel
</div>
);
And now for my actual question (sorry for the long exposition):
Let's say that i have a parent component called <Dashboard> which has all panels as its childern:
<Dashboard>
<Panel1>
<Panel2>
<Panel3>
</Dashboard>
Is there something i can write in the implementation of Dashboard that will entirely remove the need to specify anything about these modal components in each Panel?
meaning the the Panel1 implementation can now just be
<div>
...all other stuff done in panel
</div>
and when it's rendered as a child of Dashboard it will have that modal dialog and accompanying logic.
I suggest using a wrapper component with the children prop. Your parent component would look like this:
<Dashboard>
<ModalWrapper text={msg1}>
<Panel1 />
</ModalWrapper>
<ModalWrapper text={msg2}>
<Panel2 />
</ModalWrapper>
<ModalWrapper text={msg3}>
<Panel3 />
</ModalWrapper>
</Dashboard>
Now all your conditional logic can be placed in ModalWrapper. Where your question has "....all other stuff needed in Panel", use this.props.children. e.g.
var ModalWrapper = React.createClass({
render: function () {
var text = this.props.text;
return (
<div>
{text ? <Modal text={text} /> : null}
{this.props.children}
</div>
);
}
});

react.js - React-router dealing with fixed headers and footers

I have React.js app flavored with react-router, I have a doubt regarding my current routes handling.
Design looks as follows, common mobile layout, fixed header and footer, content in the middle:
In the case they are static I can simply create such structure:
<RatchetHeader />
<RatchetFooter />
<RouteHandler />
But occasionally they would change from page to page, for example:
title and button texts
number of buttons
footer is not present on some pages
Is it better to put them inside view controllers and re-render everytime with RouteHandler?
I don't know specifics of Ratchet, but in general terms of react, in your situation, it's better indeed for the footer to put it inside a RouteHandler, so that you can define its presence depending on your preferences.
For the Header, I believe you'd always like to have it there? In that case, you could leave it outside the Handler and pass it properties instead to change it's layout.
The final result would look something similar to this (the component imports are implied, therefore I'm not including them for the sake of keeping focus on the logic):
The app.js:
<Route handler={AppHandler}>
<DefaultRoute handler={HomeHandler}/>
<Route name='foo' path='/foo' handler={FooHandler}/>
<Route name='bar' path='/bar' handler={BarHandler}/>
<NotFoundRoute handler={NotFoundHandler}/>
</Route>
);
The App.react.js:
<div>
<Header title={this.state.title}/>
<RouteHandler {...this.props}/>
</div>
The Header.react.js - using some imaginary components for illustration:
var Header = React.createClass({
render: function(){
return (
<div>
<Button type="previous" title="Left"/>
<HeaderTitle>{this.props.title}</HeaderTitle>
<Button type="next" title="Right"/>
</div>
);
}
});
module.exports = Header;
The Foo.react.js:
var Foo = React.createClass({
render: function(){
var footerActions = [ // Ideally you'd get this from a constants file or something alike.
{
'actionType': 'viewHome',
'actionIcon': 'icon-home',
'actionLabel': 'Home'
},
{
'actionType': 'viewProfile',
'actionIcon': 'icon-profile',
'actionLabel': 'Profile'
},
{
'actionType': 'viewFavorites',
'actionIcon': 'icon-favorites',
'actionLabel': 'Favorites'
},
...
];
return (
<div>Your content here</div>
<Footer actions={footerActions}/>
);
}
});
module.exports = Foo;
The Footer.react.js:
var Footer = React.createClass({
render: function(){
var actionItems = this.props.actions.map(function(item){
return (<ActionItem action={item.actionType} icon={item.actionIcon} label={item.actionLabel}/>);
});
return (
<div>{actionItems}</div>
)
}
});
module.exports = Footer;
Then, in the Bar.react.js you could just not include the <Footer> component, like this:
The Bar.react.js:
var Bar = React.createClass({
render: function(){
return (
<div>Your content here</div>
);
}
});
module.exports = Bar;
Hope that helps!

Categories