I am trying to show the 'edit-btn' only if the user is logged in. What is the best way to achieve this in ReactJS? By rendering the component without the element if not logged in and rendering the element if the user is logged in? If so, what is the best approach to achieve this? I am quite new to ReactJS.
class LeftBlock extends React.Component {
render() {
return (
<div className="left-block">
{this.props.children}
<i className="fa fa-pencil"></i>
<br className="clear" />
</div>
);
}
}
You can conditionally render components, even if they are null.
class LeftBlock extends React.Component {
render() {
var isAuth = false; // You'll need to figure out a way how to get this - From a store maybe or cookie?
var button;
if (isAuth){
button = (<i className="fa fa-pencil"></i>);
}
return (
<div className="left-block">
{this.props.children}
{button}
<br className="clear" />
</div>
);
}
}
So in this case, if isAuth is false, nothing will be rendered in place of {button}.
In terms of getting the isAuth status, I'd recommend having an AuthenticationStore which you can then get authentication information from within your components.
Checkout the Flux architecture if you're not already familiar with it. https://facebook.github.io/flux/docs/overview.html
Update
Fiddle: https://jsfiddle.net/6yq1ctcp/1/
Related
I need to render a component based on the user role.
For example:
If the user is a sales persons he would see both buttons.
But if he is support he would see only the second button.
import React from 'react';
import Button from './MyCustomButton';
class App extends React.PureComponent {
render() {
return (
<Grid>
<Button> // visible only for sales manager
Action A
</Button>
<Button> // visible for support and sales manager
Action B
</Button>
</Grid>
)
}
}
I would really like to avoid if-else statements inside the render. Keep it clean as possible.
Is there a good design or some tools/decorator that could be useful here?
I've already handled it in the server side but I need a clean solution for the client side as well.
thanks.
You can inline ternary statements directly into your component tree, as shown below. If these boolean expression would evaluate to false, react will skip them automatically.
class App extends React.PureComponent {
render() {
const user = this.props.user;
return (
<Grid>
{user.isSalesManager && <Button>
Action A
</Button>}
{(user.isSalesManager || user.isSupport) && <Button>
Action B
</Button>}
</Grid>
)
}
}
In order to do that, you need to have the information about the user role in the props though.
Read more on conditional rendering in the React documentation.
One way you could do this is with an Authorizor component. It would be a very simple function that does your validation for you like normal, and just checks an authLevel. If you need to adjust this to work with your auth model thats fine.
function Authorizor(props) {
if (props.authLevel > props.user.authLevel) {
return null;
}
const { ComponentToValidate } = props;
return <ComponentToValidate {...props} />;
}
then usage would be something like
<Authorizor authLevel={1} ComponentToValidate={Button} {...this.props} />
in your example this would look like.
class App extends React.PureComponent {
render() {
return (
<Grid>
<Authorizor authLevel={2} ComponentToValidate={Button} label="Action A" onClick={this.handleActionAThingy} {...this.props} />
<Authorizor authLevel={1} ComponentToValidate={Button} label="Action B" onClick={this.handleActionBThingy} {...this.props} />
</Grid>
)
}
}
If it's just a matter of 'cleaner' look and not lack of condition (because you will have to have it somewhere) you could create simple reusable component:
const Visibility = ({ isVisible, children }) => (
isVisible ? React.Children.only(children) : null;
)
and use it like that
<Grid>
<Visibility isVisible={user.isSalesManager}>
<Button> // visible only for sales manager
Action A
</Button>
</Visibility>
<Visibility isVisible={user.isSalesManager || user.isSupport}>
<Button> // visible for support and sales manager
Action B
</Button>
</Visibility>
</Grid>
it's hard to come up with much better solution without knowing more about your application architecture, but if you keep your user roles in some kind of store accessible from anywhere (redux, context, whatever) you could create very similar component that accepts roles needed to see it's children (let's say an array of strings) and possibly some kind of descriptor (like all, oneOf etc. if your permissions system is more complicated) and component itself grabs needed data from global store and compares it to props it got. If that's your use case i can provide example.
Short code
render() {
const button1 = <Button>Action A</Button>; // visible only for sales manager
const button2 = <Button>Action B</Button>; // visible for support and sales manager
return (
<Grid>
{[user.type === 'salesManager' ? button1 : null, button2]}
</Grid>
)
}
I generally use like following in these kind of scenarios
render(){
var buttonADiv = <Button> // visible only for sales manager
Action A
</Button> ;
var buttonBDiv = <Button> // visible for support and sales manager
Action B
</Button> ;
var rows = [];
if(user === 'sales'){
rows.push(buttonADiv);
rows.push(buttonBDiv);
}
else if(user === 'support'){
rows.push(buttonBDiv);
}
return (
<Grid>
{rows}
</Grid>
)
}
import React from 'react';
import Button from './MyCustomButton';
class App extends React.PureComponent {
render() {
if(user.type = 1){
return (
);
}
else if(user.type = 2){
return (
);
}
}
}
So, I'm using react-select to let the user pick a choice from a list of options. It's supposed to update on-change. I've verified that the selected option is indeed being updated into the database, and the input is being recognized by React upon checking it in the React chrome tools. What's puzzling is how it doesn't get displayed after refreshing the page.
class ContractBasicForm extends React.Component {
constructor (props) {
super(props)
this.state = {
contractingEntity: props.contracting_entity
}
componentWillReceiveProps(nextProps) {
this.setState({
contractingEntity: nextProps.contracting_entity
})
}
autoSetState = (newState) => {
this.setState(newState)
this.props.formSubmit()
}
render () {
return(
<div className="container-fluid">
<section className="row ml-2 mr-2 mt-2">
<article className="col-12 side-modal-form">
<SelectInput
header="Contracting Entity"
name="contract[contracting_entity]"
options={this.props.contracting_entity_opts}
value={this.state.contracting_entity}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({contracting_entity: e.value})
}}
/>
</article>
</section>
</div>
)
}
}
I have another input called Stage which is very similar to ContractingEntity, but its value is displayed after refreshing the page:
<SelectInput
header="Stage"
name="contract[stage]"
options={this.props.stage_opts}
value={this.state.stage}
userCanEdit={this.props.user_can_edit}
multi={false}
onChange={(e) => {
this.autoSetState({stage: e.value})
}}
/>
React app state will be initialised on page refresh. You need to persist such data in localStorage if you want to keep it after page refresh. This is considered as anti-pattern in react and it is recommended not to use this unless it becomes necessity.
I hope this made things clear for you.
I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.
I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');
I am attempting to keep with best practices, while adhering to the documentation. Without creating to many one-off methods to handle things for a maintainability standpoint.
Anyway all in all, I am trying to achieve a state between sibling elements that is in sorts an "active" state visually at the least. With something like jQuery I would simply do..
$(document).on('.nav-component', 'click', function(e) {
$('.nav-component').removeClass('active');
$(this).addClass('active');
});
However in react, each component in it of itself is independent of the next and previous, and should remain as such per the documents.
That said, when I am handling a click event for a component I can successfully give it a state of active and inactive, toggling it on and off respectively. But I end up in a place where I have multiple "active" elements when I don't need them as such.
This is for setting up a navigation of sorts. So I want the one in use at the moment to have that active class while the rest won't
I use an app.store with reflux to set state for multiple pages/components. You can do the same passing state up to a common component but using the flux pattern is cleaner.
class AppCtrlRender extends Component {
render() {
let page = this.state.appState.currentPage;
let hideAbout = (page != 'about');
let hideHome = (page != 'home');
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
<div id='allPageSty' style={allPageSty}>
<AboutPage hide={hideAbout} />
<HomePage hide={hideHome} />
</div>
</div>
);
}
}
let getState = function() { return {appState: AppStore.getAppState(),}; };
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = AppStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = () => { this.setState(getState()); }
}
In the page/component check for this.props.hide.
export default class AboutPage extends Component {
render() {
if (this.props.hide) return null;
return (
<div style={AboutPageSty}>
React 1.4 ReFlux used for app state. This is the About Page.
<NavMenu />
</div>
);
}
}
Siblings needing to share some sort of state in React is usually a clue that you need to pull state further up the component hierarchy and have a common parent manage it (or pull it out into a state management solution such as Redux).
For sibling components where only one can be active at a time, the key piece of state you need is something which lets you identify which one is currently active and either:
pass that state to each component as a prop (so the component itself can check if it's currently active - e.g. if each item has an associated id, store the id of the currently active one in a parent component and pass it to each of them as an activeId prop)
e.g.:
var Nav1 = React.createClass({
getInitialState() {
return {activeId: null}
},
handleChange(activeId) {
this.setState({activeId})
},
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
activeId={this.state.activeId}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
or use it to derive a new prop which is passed to each component (such as an active prop to tell each component whether or not it's currently active - e.g. in the id example above, check the id of each component while rendering it: active={activeId === someObj.id})
e.g.:
var Nav2 = React.createClass({
// ... rest as per Nav1...
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
active={this.state.activeId === item.id}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
The trick with React is to think of your UI in terms of the state you need to render if from scratch (as if you were rendering on the server), instead of thinking in terms of individual DOM changes needed to make the UI reflect state changes (as in your jQuery example), as React handles making those individual DOM changes for you based on complete renderings from two different states.