I'm working on React with Symfony API and when I connect to my app, I've got a role defined by Symfony
It returns this if I'm an admin : ["ROLE_USER", "ROLE_ADMIN"]
It returns this if I'm a moderator : ["ROLE_USER", "ROLE_MODERATOR"]
It returns this if I'm a user : ["ROLE_USER"]
Currently my code is working fine and if I'm a user, it shows the user view, if I'm a moderator it shows the moderator view etc.
So my question is : Is there a better way to create a condition that will render the good component in function of my user role ?
render()
{
let content = "";
if (this.props.auth.user.roles.includes("ROLE_ADMIN")) {
content = <NavAdminDashboard />;
} else if (this.props.auth.user.roles.includes("ROLE_MODERATOR")) {
content = <NavModeratorDashboard />;
} else {
content = <NavUserDashboard />;
}
return (
<Fragment>
{content}
</Fragment>
)
}
I have checked this : Render component based on a variable - reactjs
It is better than my code but it only works if my roles render as string and not as array like my code.
You can achieve this in two ways
The first one is a little cleaner.
render(){
const {roles} = this.props.auth.user;
return (
<React.Fragment>
{ roles.include("ROLE_ADMIN") && <NavAdminDashboard /> }
{ roles.include("ROLE_MODERATOR") && <NavModeratorDashboard /> }
{ !roles.include("ROLE_ADMIN") && !roles.include("ROLE_MODERATOR) && <NavUserDashboard /> }
</React.Fragment>
)
}
You can also do that by creating two methods isAdmin and isModerator:
isAdmin = () => {
return this.props.auth.user.roles.include("ROLE_ADMIN");
}
isModerator = () => {
return this.props.auth.user.roles.include("ROLE_MODERATOR");
}
render() {
return (
<React.Fragment>
{ this.isAdmin() && <NavAdminDashboard /> }
{ this.isModerator() && <NavModeratorDashboard /> }
{ !this.isAdmin() && !this.isModerator() && <NavUserDashboard /> }
</React.Fragment>
)
}
Or you can add a isUser method to check if its only user
isUser = () => {
const {roles} = this.props.auth.user;
return roles.include("ROLE_USER") && roles.length === 1;
}
render() {
return (
<React.Fragment>
{ this.isAdmin() && <NavAdminDashboard /> }
{ this.isModerator() && <NavModeratorDashboard /> }
{ this.isUser() && <NavUserDashboard /> }
</React.Fragment>
)
}
I think your code is fine and doesn't necessary need to change. But I personally move the role logic either to external functions (that can be unit tested) or methods on the component. Eg:
get isAdmin() {
return this.props.roles.include('ADMIN');
}
get isUser() {
return !this.props.roles.some(role => role !== 'USER');
}
render() {
return <>
{this.isAdmin && <Admin />}
{this.isUser && <User />}
</>
}
Another alternative is to move the parsing of roles to a helper function and map the array to props. Eg:
<Component isAdmin={hasAdminRole(roles)} />
Both of these are nicer solutions if you ask me. But in the end, as long as the code works it might be good enough. You can always go back and refactor later.
Related
I've got a list of products to render. I also have an array of keywords that are used to exclude products from being rendered.
I am looping over the array of keywords and checking if the product title contains any of the entries. It then returns a boolean.
The following code does not work. The console.log works and reflects the result but nothing is rendered.
function inExcludes(product, excludedItems) {
excludedItems.forEach( item => {
if (product.includes(item)) {
console.log(true);
return true;
} else {
console.log(false);
return false;
}
})
}
export function CardsFiltered(props) {
const cards = props.items.map((product) => {
if (inExcludes(product.title, props.excludedItems) === false)
return (
<CardValidation
key={product.id}
id={product.id}
product={product}
/>
)
})
return (
<>
{cards}
</>
);
}
But if I set a variable as a boolean, switch that variable if the condition is true, and then return that variable, it works and my cards are rendered (code below).
Is anyone able to shed light on this? Because I can't figure it out.
function inExcludes(product, excludedItems) {
let result = false;
excludedItems.forEach( item => {
if (product.includes(item)) {
result = true;
}
})
return result;
}
export function CardsFiltered(props) {
const cards = props.items.map((product) => {
if (!inExcludes(product.title, props.excludedItems))
return (
<CardValidation
key={product.id}
id={product.id}
product={product}
/>
)
})
return (
<>
{cards}
</>
);
}
Your first implementation of 'inExcludes' isn't returning a boolean (true/false). It's just executing the 'forEach' on each item in the excludedItems array. The return within that loop doesn't return from the function as a whole.
So, as it effectively returns 'undefined' your render decides not to render anything.
Here's something that does what you're after (simplified a bit):
https://codesandbox.io/s/awesome-mcclintock-hkkhsi?file=/src/App.js
I am having trouble understanding why I cannot get images to show up in my components. I have a boolean which indicates loading, and an array that gets filled async. When I finish, I set the boolean and the component re renders. Now, I want to create a card for each item in the array and put in in a card deck (this is from react-bootstrap if that wasn't obvious). I can do this with any given boolean and array, but not with the boolean and arrays created with React.useState... Why is that and how should I go about fixing this?
I encountered this problem quite a few hours ago, and have tracked down its source to this minimal working example that still reflects what I am trying to do, but I am unsure of what to do from here.
function TestCard() {
return (
<Card>
<Card.Img src="holder.js/200x200" />
</Card>
);
}
I am trying to render the following component:
function MainComponent() {
const [boolState, setBoolState] = React.useState(false);
const [arrayState, setArrayState] = React.useState([]);
React.useEffect(() => {
setTimeout(() => {
setBoolState(true);
setArrayState([1,2,3]);
}, 2000);
});
return (
<>
{/* This works */}
{
true &&
<CardDeck>
{
[1,2,3].map(_ => {
return (
<TestCard />
);
})
}
</CardDeck>
}
{/* This doesn't, why? */}
{
boolState &&
<CardDeck>
{
arrayState.map(_ => {
return (
<TestCard />
);
})
}
</CardDeck>
}
</>
);
}
Code sandbox
I hope this question isn't too subjective. I've been doing a lot of React development lately, and I have a header that used to have two different renderings.
Recently my client has asked for two additional renderings. I could nest conditionals in the renderings, but this feels messy to me and against good practices.
For example I have this:
{this.state.headerLoc ? (
<div className="secondary-nav">
...
</div>
) : (
<Back />
)}
I'd like to add two additional conditions to this - is there a "clean" way to do this without a bunch of additional nesting? Would refactoring/subcomponents be the only way to handle this condition?
EDIT: Pseudo-code example of what I want to do:
render {
if(page == 'page1') {
<renderX />
}
else if(page == 'page2') {
<renderX2 />
}
else if(page == 'page3') {
<renderX3 />
}
else if(page == 'page4') {
<renderX4 />
}
}
EDIT: Update for what I am doing now:
const HeaderArrays = {
FrontPage: ["/"],
SearchPage: ["cards", "map", "detail"],
NonSearchPage:[],
NonSearchPageHamburger:["settings"],
}
HeaderComponents() {
var routerPath = this.props.router.location.pathname;
for (const component in HeaderArrays) {
const value = HeaderArrays[component];
if(HeaderArrays[component].includes(routerPath)) {
return component;
}
return "FrontPage";
}
render() {
const ComponentToRender = this.HeaderComponents();
return(
<ComponentToRender />
You can just map components to a key in an object. this way you can omit a bunch of if else statements
const HeaderComponents = {
page1: Page1HeaderComponent,
page2: Page2HeaderComponent,
page3: Page3HeaderComponent,
page4: Page4HeaderComponent
}
and usage would be
render() {
const { page } = this.props // assuming page comes from props
const ComponentToRender = HeaderComponents[page]
return <ComponentToRender />
}
Here's an example to play with :)
I'm trying to return an HTML element or another depending on some conditions calculated on Javascript. I tried doing this, but I can't start the condition with and if, I don't understand why.
My component file is this one:
import React from 'react';
import defaultImage from './defaultImage.jpg';
export default class Game extends React.Component {
render() {
const image = this.props.question.attachment.url;
const tips = this.props.question.tips;
return (
<div className="flexDisplay">
<img src={image === (null || "") ? defaultImage : image} className="questionImage centerVertical" alt="Error loading, just read the question" />
<div className="centerHorizontal centerVertical">
<h1>{this.props.question.question}</h1>
<h2 className="centerHorizontal">Pistas:</h2>
{
if(tips.length === 0){ //The problem comes here
return <div>No hay pistas disponibles</div>
}else{
tips.map((tip, i,) => {
return <div className="centerHorizontal" key={tip.toString()}>{i+1}. {tip}</div>;
})
}
}
</div>
</div>
);
}
Anyone spot the problem?
You can not use if statements inside JSX syntax. Instead you can use the ternary operator which basically accomplish the same :
{
tips.length === 0 ?
(<div>No hay pistas disponibles</div>)
: (tips.map((tip, i,) => {
return <div className="centerHorizontal" key={tip.toString()}>{i+1}. {tip}</div>;
}));
}
In ReactJS's component ( JSX ) you are not allowed to use anything else than a statement that returns a value.
You can imagine the logic by trying to assign a variable :
const result = if ( a ) { "b" } else { "c" } // won't work
But on the other hand with a Ternary Operator it will.
const result = a ? "b" : "c";
So in your case there are two ways of achieving the goal :
{ tips.length === 0 ? ( <div>No hay pistas disponibles</div> ) : (
tips.map((tip, i) => (
<div className="centerHorizontal" key={ tip.toString() }>{i+1}. {tip}</div>
) )
) }
Or you can simply extract that in a method
renderTips( tips ) {
if ( tips.length === 0 ) { return null; }
return tips.map( ( tip, i ) => (
<div className="centerHorizontal" key={ tip.toString() }>{i+1}. {tip}</div>
);
}
render() {
...
return (
...
{ this.renderTips( tips ) }
)
}
You're not able to use "if" in inline conditional statements in jsx. You can however use the ternary syntax instead:
{
tips.length === 0 ? (
return <div>No hay pistas disponibles</div>
) : (
tips.map((tip, i,) => {
return <div className="centerHorizontal" key={tip.toString()}>{i+1}. {tip}</div>;
})
)
}
You can read more about using inline conditional statements here: https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator
in many of my components I am fetching API data and therefor I need to wait until that data was loaded. Otherwise I am getting errors because some methods are, of course, not available.
My api query looks like this
componentDidMount() {
prismicApi(prismicEndpoint).then((api) =>
api.form('everything')
.ref(api.master())
.query(Prismic.Predicates.at("my.page.uid", this.props.params.uid))
.submit((err, res) => {
if (res.results.length > 0) {
this.setState({doc: res.results[0]});
} else {
this.setState({notFound: true});
}
}))
}
For that I've created this structure that I have been using in all of these documents:
render() {
if (this.state.notFound) {
return (<Error404 />);
} else if (this.state.doc == null || !this.state.doc) {
return (<Loading />);
} else {
return (
<div className="page">
{this.state.doc.getSliceZone('page.body').slices.map(function(slice, i){
return (<SliceZone slice={slice} key={i} />)
})}
</div>
)
}
}
I wanted to move this into a component called Document that looks like this here:
export default class Document extends React.Component {
static defaultProps = {
doc: null,
notFound: false
}
static propTypes = {
doc: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
notFound: React.PropTypes.bool.isRequired
}
render() {
if (this.props.notFound) {
return (<Error404 />);
} else if (this.props.doc == null || !this.props.doc) {
return (<Loading />);
} else {
return (
<div className="page">
{this.props.children}
</div>
)
}
}
}
and then I tried to use it like this here:
<Document doc={this.state.doc} notFound={this.state.notFound}>
{this.state.doc.getSliceZone('page.body').slices.map(function(slice, i){
return (<SliceZone slice={slice} key={i} />)
})}
</Document>
Though on the second example the error messages are showing up quickly (until the data is loaded) and then disappear. What am I doing wrong? Why is the first example working and the second doesnt?
try this
<Document doc={this.state.doc} notFound={this.state.notFound}>
{ this.state.doc && this.state.doc.getSliceZone('page.body').slices.map(function(slice, i){
return (<SliceZone slice={slice} key={i} />)
})}
</Document>
in your variant, you see an error becuase this.state.doc is null, untill data is loaded, and you see null reference exception, looks like.
In 1st case, it does not calculate, in 2nd case it calculates first and then sent as a parameter "children" to your Document control