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 :)
Related
I have a component that is rather complex, so I build it from nested components. Simplified:
export function BlockProvideFeedback(props) {
const handleSelect = (event) => console.log(event);
const Form = (props) => (
<>
<RadioList selected={props.selected} />
<Button onClick={handleSelect} />
</>
);
const Message = () => (
<p>Thanks for the feedback</p>
);
if (props.feedbackStatus == 'agreed') {
return(<Form selected='done'/>);
if (props.feedbackStatus == 'pending') {
return(<Form selected='needswork'/>);
} else {
return(<Message/>);
}
};
BlockProvideFeedback.propTypes = {
feedbackStatus: PropTypes.string.isRequired,
};
The linter is, rightfully IMO, pointing out that props.selected in Form(props) needs propTypes. But I don't know where and how to provide them. 'selected' is missing in props validation.
I tried the obvious:
BlockProvideFeedback().Form.propTypes { //... }
But that throws errors because I'm instantiating the component (by calling the function) without the proper context, props, providers etc.
How do I put selected for my nested component, in a props validation?
Or am I maybe doing something else horribly wrong: for example, my entire setup of nesting the maybe is so non-react-ish that tooling, like linters, will fail on it without being able to solve it?
If Form and Message are not intended to be shared and reused, I would convert them in "render functions"; something like:
export function BlockProvideFeedback(props) {
const handleSelect = (event) => console.log(event);
const renderForm = (selectedVal) => (
<>
<RadioList selected={selectedVal} />
<Button onClick={handleSelect} />
</>
);
const renderMessage = () => (
<p>Thanks for the feedback</p>
);
if (props.feedbackStatus == 'agreed') {
return renderForm('done');
if (props.feedbackStatus == 'pending') {
return renderForm('needswork');
} else {
return renderMessage();
}
};
BlockProvideFeedback.propTypes = {
feedbackStatus: PropTypes.string.isRequired,
};
Not really an answer to your question, I know, but it seems to me this case can be a sort of "XY problem".
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
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.
I am new to reactjs and I'm creating a project using it. I have a problem while using multiple components. Here is the scenario
when the page is loaded both component is loaded values of type,getcpeyearlysval and getvnfyearlysval are set
setDataMeter1(){
//here i want only type cpe componnet will load
}
<Speedometer type = "cpe" getcpeyearlysval={this.objSpeedometer.getcpeyearlys}/>
<Speedometer type = "vnf" getvnfyearlysval={this.objSpeedometer.getvnfyearlys}/>
Now I want when I to call this.setDataMeter1 function only type cpe component should work, but the problem is both calls.
<select value={this.state.selectValue} onChange={this.setDataMeter1}>
In Speedometer component:
renderIcon(key, val) {
if(key == 'vnf') {
console.log("vnf")
}
if(key == 'cpe'){
console.log("cpe")// this condition should met
}
}
render() {
return (
this.renderIcon(this.props.type, this.props)
);
}
}
But my problem is both conditions mets in speedometer components.
renderIcon(props) {
if(props.type == 'vnf') {
return <Text>hey this is vnf </Text>
}
if(props.type == 'cpe'){
return <Text>hey this is cpe </Text>
}
}
render() {
return (
this.renderIcon(this.props)
);
}
So, React newbie here... I'll start off by saying I have a simple single page application which consists of a few simple pages.
Using react-router I have a 'top-down' set up for my components. To give you a basic idea of my SPA structure see below:
index -- layout(react routers) --
|--About Page
|--Home Page
|--Contact Page
I am rendering a component called "GlobalHero" from my Home Page component.
Here is the GlobalHero.jsx component.
import React from "react";
var classNames = require('classnames');
import s from '../../../index.scss';
class GlobalHero extends React.Component {
constructor() {
super();
//sets initial state
this.state = {
fadeIn: "",
titleSelected: "",
subTitleSelected: ""
};
}
// <<========= ON COMPONENT RENDER =========
componentDidMount = () => {
console.log("GlobalHero");
console.log(this.props);
this.handleClass("fadeIn");
}
// =========>>
// <<========= CONTROLS THE STATE =========
handleClass = (param) => {
if (param === "fadeIn" && this.state.fadeIn != "true") {
this.setState({fadeIn: "true"});
}
if (param === "titleSelected" && this.state.titleSelected != "true") {
this.setState({titleSelected: "true"});
}
if (param === "subTitleSelected" && this.state.subTitleSelected != "true") {
this.setState({subTitleSelected: "true"});
}
}
// =========>>
render() {
const heroImg = require(`../../../images/hero${this.props.page}.jpg`);
//REMOVES CLASS IN REALTIME BASED ON STATE'S VALUE =========
var containerClasses = classNames({
[s['text-center']]: true,
[s["hidden"]]: this.state.fadeIn != "true",
[s["fadeIn"]]: this.state.fadeIn === "true"
});
var titleClasses = classNames({
[s['blue']]: this.state.titleSelected === "true"
});
var subTitleClasses = classNames({
[s['subTitle']]: true,
[s['text-center']]: true,
[s['blue']]: this.state.subTitleSelected === "true"
});
// =========>>
return (
<div className={s["container-fluid"]}>
<div className={s["row"]}>
<div className={s["col-lg-16"]}>
<div className={containerClasses}>
<img src={heroImg} className={s["hero__img"]}></img>
<h1 onClick={() => this.handleClass("titleSelected")} className={titleClasses}>{this.props.page}!</h1>
<p className={subTitleClasses} onClick={() => this.handleClass("subTitleSelected")}>{this.props.name}, {this.props.age}, {this.props.city}</p>
</div>
</div>
</div>
</div>
);
}
}
export default GlobalHero;
I noticed there is a lot of complexity there for assigning a few simple class names to the component's elements.
I was wondering if there is a better practice for doing this? Maybe
using an external js page to manage my classnames?
Any input or adivce is appreciated... Thankyou in adnvance.
Your title mentions BEM but it looks like you are using CSS Modules, which is inspired by similar ideas but not the same thing.
Anyway, this is quite subjective but I have a few thoughts that are too much to fit in a comment:
Assuming you are using css modules through Webpack's css-loader, you can use camelCase to make your style properties more JS friendly:
loader: "css-loader?modules&camelCase"
Now for .text-center css class name you can simply use s.textCenter instead of s["test-center"].
You could componentize this better: first, you are kind of doing a lot for a single component, but you could break it down into a few smaller components that each have a single responsibility (for example container, title, subtitle). Second, your handleClass() method is doing a lot, when you could just have simple handlers that call setState() without knowing anything about class names. In other words, the component should have props and state, only the render() function deals with how to translate that into class names to render. You also really don't need to check the state's current value before setting it. Just set it to what it should be and let React optimize rendering performance for you.
You have boolean state flags that you store using strings "true" and "false"... this makes it noisy to handle, just store as booleans.
You have a lot of [s["class-name"]]: true which is not necessary; if you always want a class name to be rendered just pass it as an argument to classNames:
classNames(s.subTitle, { [s.blue]: this.state.subTitleSelected })
There's no reason to call a handler on componentDidMount, just initialize the state how you want it.
It looks like you're using bootstrap CSS but not the React Bootstrap components. I would highly recommend using React Bootstrap.
Putting that together I'd have something like:
class GlobalHero extends React.Component {
state = {
fadeIn: true,
titleSelected: false,
subTitleSelected: false
};
handleTitleClick = () => {
this.setState({titleSelected: true});
};
handleSubTitleClick = () => {
this.setState({subTitleSelected: true});
};
render() {
return (
<Grid fluid>
<Row>
<Col lg={16}>
<HeroContainer fadeIn={this.state.fadeIn}>
<HeroImage page={this.props.page} />
<HeroTitle selected={this.state.titleSelected}
onClick={this.handleTitleClick}
page={this.props.page} />
<HeroSubTitle selected={this.state.subTitleSelected}
onClick={this.handleSubTitleClick}
name={this.props.name}
age={this.props.age}
city={this.props.city} />
</HeroContainer>
</Col>
</Row>
</Grid>
);
}
}
const HeroContainer = ({fadeIn, children}) => {
return (
<div className={classNames(s.textCenter, fadeIn ? s.fadeIn : s.hidden)}>
{children}
</div>
);
};
const HeroImage = ({page}) => {
const heroImg = require(`../../../images/hero${page}.jpg`);
return (
<img src={heroImg} className={s.heroImg} />
);
};
const HeroTitle = ({onClick, selected, page}) => (
<h1 onClick={onClick} className={selected ? s.blue : null}>{page}!</h1>
);
const HeroSubTitle = ({onClick, selected, name, age, city}) => (
<p className={classNames(s.subTitle, s.textCenter, { [s.blue]: selected })} onClick={onClick}>
{name}, {age}, {city}
</p>
);
Breaking it into smaller components like this is not completely necessary, but notice how from the perspective of GlobalHero it does nothing with styles, it just sets props and state, and the little parts have no state, they just render the correct styles based on props.
PS maybe this should move to Code Reviews?