Conditionally rendering component sections in React JSX - javascript

According to MDN "You can also do more than one single operation per case, separating them with a comma." The example below works:
var stop = false, age = 23;
age > 18 ? (
alert("1"),
alert("2")
) : (
stop = true,
alert("Sorry, you are much too young!")
);
But I can't seem to do the same in React as seen below. I expect both "Yes" and "No" buttons to be displayed, but it as it only displays the "No" button.
return (
<div className="topcoat-list">
<ul className="topcoat-list__container">
{
notes.map(function (note) {
var title = note.content.substring(0, note.content.indexOf("\n"));
title = title || note.content;
var toggleDeleteDialogs = this.state.isConfirming && note.id === notepad.selectedId;
var disableDelete = this.state.isConfirming && note.id !== notepad.selectedId;
return (
<li key={note.id} onClick={this.onSelectNote.bind(null, note.id)} className="topcoat-list__item">
{title}
{
toggleDeleteDialogs ?
(
<button key={note.id} onClick={this.deleteThisNote.bind(null, note.id)} className="half">Yes</button>,
<button className="half" onClick={this.onCancelDelete}>No</button>
) : (
<button key={note.id} onClick={this.deleteThisNote.bind(null, note.id)} className="full" disabled={disableDelete ? "disabled" : ""}>Delete Note</button>
)
}
</li>
);
}.bind(this))
}
</ul>
</div>
);
Full markup: https://jsfiddle.net/55fvpcLo/
Is my syntax off or could this be done more elegantly?

The fiddle doesn't seem to be working, but I can reproduce the behavior. Although it doesn't raise the Adjacent JSX elements must be wrapped in an enclosing tag error, I suspect that that may be the reason it doesn't work, since adjacent elements is effectively what you're trying to do.
I think the simplest solution is just to wrap the two elements in an enclosing tag rather than parentheses.

You could also return an array of JSX-components, e.g.
{
toggleDeleteDialogs ?
[<Button ... />, <Button ... />] :
<Button .../>
}

#Adam Stone is right that the problem is that there are adjacent JSX elements not wrapped in a closing tag.
That said, you asked for the most elegant way to solve the problem.
I made the following changes to your code:
Used this function to selectively hide JSX elements:
var hideIfFalse=function(boolean){
return boolean? {} : {display : 'none'};
};
which you can use like this:
<div style={hideIfFalse(toggleDeleteDialogs)} />
Separated the logic for rendering the list items into a renderChildren method:
renderChildren:function(notes,classes){
return notes.map(function (note) {
//...
Made a DeleteDialog component. It has reusable functionality with its own rendering logic, and separating it out improves code readability:
var DeleteDialog=React.createClass({
render:function(){
var classes=this.props.classes;
return <div style={hideIfFalse(this.props.toggleDeleteDialogs)}>
<button onClick={this.props.onDelete} className="half">
Yes
</button>,
<button className="half" onClick={this.props.onCancelDelete}>
No
</button>
</div>
}
});
I didn't touch the classSet logic but don't understand what it's supposed to do.
Putting it all together:
var hideIfFalse=function(boolean){
return boolean? {} : {display : 'none'};
};
var notepad = {
notes:
[
{
id: 1,
content: "Hello, world!\nBoring.\nBoring.\nBoring."
},
{
id: 2,
content: "React is awesome.\nSeriously, it's the greatest."
},
{
id: 3,
content: "Robots are pretty cool.\nRobots are awesome, until they take over."
},
{
id: 4,
content: "Monkeys.\nWho doesn't love monkeys?"
}
],
selectedId: 1
};
var DeleteDialog=React.createClass({
render:function(){
var classes=this.props.classes;
return <div style={hideIfFalse(this.props.toggleDeleteDialogs)}>
<button onClick={this.props.onDelete} className="half">
Yes
</button>,
<button className="half" onClick={this.props.onCancelDelete}>
No
</button>
</div>
}
})
var NotesList = React.createClass({
getInitialState: function() {
return {
isConfirming: false
};
},
onSelectNote: function(id) {
notepad.selectedId = id;
},
deleteThisNote: function(noteId) {
if(this.state.isConfirming) {
// actual delete functionality should be here
this.setState({isConfirming: false});
}
else {
this.setState({isConfirming: true});
}
},
onCancelDelete: function() {
this.setState({ isConfirming: false });
},
renderChildren:function(notes,classes){
return notes.map(function (note) {
var title = note.content.substring(0, note.content.indexOf("\n"));
title = title || note.content;
var toggleDeleteDialogs = this.state.isConfirming && note.id === notepad.selectedId;
var disableDelete = this.state.isConfirming && note.id !== notepad.selectedId;
return <li key={note.id}
onClick={this.onSelectNote.bind(null, note.id)}
className="topcoat-list__item">
{title}
<button key={note.id} onClick={this.deleteThisNote.bind(null, note.id)} className="full" disabled={disableDelete ? "disabled" : ""}>Delete Note</button>
<DeleteDialog
toggleDeleteDialogs={toggleDeleteDialogs}
note={note}
onDelete={this.deleteThisNote.bind(null, note.id)}
onCancelDelete={this.onCancelDelete.bind(this)} />
</li>
}.bind(this))
},
render: function() {
var notes = notepad.notes;
var cx = React.addons.classSet;
var classes = cx({
"topcoat-button-bar__button": true,
"full": !this.state.isConfirming,
"half": this.state.isConfirming,
});
return (
<div className="topcoat-list">
<ul className="topcoat-list__container">
{this.renderChildren(notes,classes)}
</ul>
</div>
);
}
});
React.render(<NotesList />, document.getElementById('container'));
JSFiddle: http://jsfiddle.net/55fvpcLo/2/

Related

Reactjs - Building form from data set, FormElement not returning to the render function

I am dynamically building a form based on the state build in the constructor. I am having success building the outer html but the inner form html is not rendering. cAN SOMEONE POINT OUT WHAT i AM DOING WRONG HERE?
class Forms extends Component {
constructor(props) {
super(props);
this.state = {
enrollment: {
class: "form-style",
fieldsets: [{
id: "1",
title: "Company Enrollment Form",
formElements: [{
label: "Company Name:",
element: "input",
type: "text",
class: "",
name: "cName",
placeholder: "Your Company's Name *",
required: true,
disabled: false
}, {
label: "Company Type:",
element: "select",
type: "populateDDL",
class: "",
name: "sltCompanyType",
placeholder: "",
required: true,
disabled: false
}]
}]
}
}
}
render() {
let Content = null;
if (this.props.type === "enrollment") {
Content = <EnrollmentForm state={this.state.enrollment} />
} else if (this.props.type === "contact") {
Content = <ContactUsForm />
} else {
Content = <fourOhFour />
}
return (
<div className="container formContent">
{Content}
</div>
);
}
};
function EnrollmentForm(form) {
function renderFieldsets(fieldsets) {
if (fieldsets.length > 0) {
return fieldsets.map((fieldset, index) => (
<Fieldset key={index} set={fieldset} />
));
}
else return [];
}
function renderFormElements(formElements) {
if (formElements.length > 0) {
return formElements.map((formElement, i) => (
<FormElement key={i} set={formElement} />
));
}
else return [];
}
const FormElement = (props, index) => {
console.log(props);
/* ^^^ NOT APPEARING/LOGGING IN THE CONSOLE ^^^ */
if (props.tag === "input") {
return (
<input key={index} name={props.name} />
);
}else if (props.tag === "select") {
return (
<select key={index} />
);
}
};
const Fieldset = (props, index) => {
const elements = renderFormElements(props.set.formElements);
return (
<fieldset key={index}>
<legend>
<span className="number fa fa-address-card"></span>
{props.set.title}
</legend>
</fieldset>
);
};
const fieldsets = renderFieldsets(form.state.fieldsets);
return (
<form className={form.state.class}>
{fieldsets}
</form>
);
}
The FormElement variable is not returning the html... I need to return a different type of html element based on what the tag is in the data model. In the code I have successfully created the outer fieldset but when I go to insert the html elements inside the fieldset, it doesn't do anything. I put a console.log in the code block but apparently even when I try to use the "FormElement" the code isn't firing ...
You have bugs in your code.
Your FormElement should look like
const FormElement = (props, index) => {
console.log(props);
/* ^^^ NOT APPEARING/LOGGING IN THE CONSOLE ^^^ */
if (props.set.element === "input") {//changed from props.tags-- 1
return (
<input key={index} name={props.name} />
);
}else if (props.set.element === "select") {//changed from props.tags --1
return (
<select key={index} />
);
}
return <div>Something which is not select or input</div>; //added a fallback return. --1
};
The bugs that were fixed above are:
(1) props.tags changed to props.set.element. There was no tags component in the props. So none of the if blocks were being rendered and this led to the component not returning anything. This led to an error that said
FormElement(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
This was resolved by adding the fallback return statement (2).
Your FieldSets should look like
const Fieldset = (props, index) => {
const elements = renderFormElements(props.set.formElements);
return (
<fieldset key={index}>
<legend>
<span className="number fa fa-address-card"></span>
{props.set.title}
</legend>
<div>{elements}</div> // consumed the elements that was created in renderFormElements.
</fieldset>
);
};
The elements variable was never used in render, which led to no FormElements showing up. That was the only error fixed in the above code.
I would suggest cleaning up your code by
Converting EnrollmentForm to a class.
Moving FieldSet and FormElement to seperate classes or at least funtional components
That would make the logic a lot easier to debug in future.

Set state for only for self data into map on reactjs

I have a object's array of users and i'm using map to show them, each user have a option buttons that is 'edit' and 'remove' options each option have a onlclick function that set a state to show another view so the code explain itselft
class App extends React.Component {
state = {
edit: false,
remove: false
}
handleEdit = () => {
this.setState({ edit: true })
}
handleRemove = () => {
this.setState({ remove: true })
}
cancelEdit = () => {
this.setState({ edit: false })
}
cancelRemove = () => {
this.setState({ remove: false })
}
renderEditItem = () => {
const {
state: {
edit,
remove
},
cancelEdit,
cancelRemove,
handleEdit,
handleRemove
} = this
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
if (remove) {
return (
<div>
<span>Remove view</span>
<br/>
<button onClick={cancelRemove}>Cancel</button>
</div>
)
}
return (
<div>
<button onClick={handleEdit}>Edit</button>
<br/>
<button onClick={handleRemove}>Remove</button>
</div>
)
}
renderUsers = () => {
const {
renderEditItem
} = this
const users = [
{
id: 1,
name: 'User1'
},
{
id: 2,
name: 'User-2'
},
{
id: 3,
name: 'User-3'
}
]
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{renderEditItem()}
</div>
</li>
</ul>
)
})
}
render () {
return (
<div>
{this.renderUsers()}
</div>
)
}
}
React.render(
<App />,
document.getElementById('app')
);
JSfiddle: Here
The issue is how can you see is, when i click on the button to set the state for edit or remove option, this will show the view for all the items,
and should be only the view that is clicked, i know the state change to true and is the same for all the items but i don't know how to set the state only for one entry any idea?
Thank you in advance.
Your problem is that the edit/remove state is singular and for the entire list. Each item in the list receives the same state here:
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
The single edit variable from the state is applied to each list item. If you want to individually set the edit state for each item, it will need to be kept track of with that item.
EX:
const users = [
{
id: 1,
name: 'User1',
edit: true
}]
This way each individual item will be able to tell what state it is in individually. User1 item will have an edit mode that is independent of the other users.
Then you can render something like this:
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{user.edit ? 'EDIT MODE' : 'NOT EDIT MODE'}
</div>
</li>
</ul>
)
})

Change color of selected element - React

I'm new to React.
I'm trying to change the color of one particular "li" that was selected, but instead it changes color of all "li".
Also when another "li" is clicked I want the first "i" to be not active again.
here is the code: http://codepen.io/polinaz/pen/zNJKqO
var List = React.createClass({
getInitialState: function(){
return { color: ''}
},
changeColor: function(){
var newColor = this.state.color == '' ? 'blue' : '';
this.setState({ color : newColor})
},
render: function () {
return (
<div>
<li style={{background:this.state.color}} onClick={this.changeColor}>one</li>
<li style={{background:this.state.color}} onClick={this.changeColor}>two</li>
<li style={{background:this.state.color}} onClick={this.changeColor}>three</li>
</div>
);
}
});
ReactDOM.render(
<List/>,
document.getElementById('app')
);
Since you don't have any identifiers on you list items you activate/deactivate them all every time. You need to reference each of them in a different way, then you can set the color individually. This is one example
var List = React.createClass({
getInitialState: function(){
return { active: null}
},
toggle: function(position){
if (this.state.active === position) {
this.setState({active : null})
} else {
this.setState({active : position})
}
},
myColor: function(position) {
if (this.state.active === position) {
return "blue";
}
return "";
},
render: function () {
return (
<div>
<li style={{background: this.myColor(0)}} onClick={() => {this.toggle(0)}}>one</li>
<li style={{background: this.myColor(1)}} onClick={() => {this.toggle(1)}}>two</li>
<li style={{background: this.myColor(2)}} onClick={() => {this.toggle(2)}}>three</li>
</div>
);
}
});
ReactDOM.render(
<List/>,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app">
<!-- This div's content will be managed by React. -->
</div>

React - send function props to Children

I saw some questions speaking about similar issues but somehow I still do not manage to solve my issue so here I am asking for your kind help. I am pretty new to React and would like to send a function from a Parent to a child and then use it from the Child but somehow when I want to use it it says
Uncaught TypeError: Cannot read property 'props' of undefined"
Edited Code after first answers were helping:
var Menu = React.createClass({
links : [
{key : 1, name : "help", click : this.props.changePageHelp}
],
render : function() {
var menuItem = this.links.map(function(link){
return (
<li key={link.key} className="menu-help menu-link" onClick={link.click}>{link.name}</li>
)
});
return (
<ul>
{menuItem}
</ul>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function() {
console.log('help');
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
Pass a value from Menu function and recieve it in the changePageHelp function and it works.
var Menu = React.createClass({
render : function() {
return (
<div>
{this.props.changePageHelp('Hello')}
</div>
)
}
});
var Admin = React.createClass ({
_changePageHelp : function(help) {
return help;
},
render : function () {
return (
<div>
<div id="menu-admin"><Menu changePageHelp={this._changePageHelp.bind(this)} /></div>
</div>
)
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="admin"></div>
For performance reasons, you should avoid using bind or arrow functions in JSX props. This is because a copy of the event handling function is created for every instance generated by the map() function. This is explained here: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
To avoid this you can pull the repeated section into its own component. Here is a demo: http://codepen.io/PiotrBerebecki/pen/EgvjmZ The console.log() call in your parent component receives now the name of the link. You could use it for example in React Router.
var Admin = React.createClass ({
_changePageHelp : function(name) {
console.log(name);
},
render : function () {
return (
<div>
<div id="menu-admin">
<Menu changePageHelp={this._changePageHelp} />
</div>
</div>
);
}
});
var Menu = React.createClass({
getDefaultProps: function() {
return {
links: [
{key: 1, name: 'help'},
{key: 2, name: 'about'},
{key: 3, name: 'contact'}
]
};
},
render: function() {
var menuItem = this.props.links.map((link) => {
return (
<MenuItem key={link.key}
name={link.name}
changePageHelp={this.props.changePageHelp}
className="menu-help menu-link" />
);
});
return (
<ul>
{menuItem}
</ul>
);
}
});
var MenuItem = React.createClass ({
handleClick: function() {
this.props.changePageHelp(this.props.name);
},
render : function () {
return (
<li onClick={this.handleClick}>
Click me to console log in Admin component <b>{this.props.name}</b>
</li>
);
}
});
ReactDOM.render(<Admin />, document.getElementById('admin'));

How to add a class with React.js?

I need to add the class active after clicking on the button and remove all other active classes.
Look here please: https://codepen.io/azat-io/pen/RWjyZX
var Tags = React.createClass({
setFilter: function(filter) {
this.props.onChangeFilter(filter);
},
render: function() {
return <div className="tags">
<button className="btn active" onClick={this.setFilter.bind(this, '')}>All</button>
<button className="btn" onClick={this.setFilter.bind(this, 'male')}>male</button>
<button className="btn" onClick={this.setFilter.bind(this, 'female')}>female</button>
<button className="btn" onClick={this.setFilter.bind(this, 'child')}>child</button>
<button className="btn" onClick={this.setFilter.bind(this, 'blonde')}>blonde</button>
</div>
}
});
var Kid = React.createClass({
render: function() {
return <ul>
<li>{this.props.name}</li>
</ul>
}
});
var List = React.createClass({
getInitialState: function() {
return {
filter: ''
};
},
changeFilter: function(filter) {
this.setState({
filter: filter
});
},
render: function() {
var list = this.props.Data;
if (this.state.filter !== '') {
list = list.filter((i)=> i.tags.indexOf(this.state.filter) !== -1);
console.log(list);
}
list = list.map(function(Props){
return <Kid {...Props} />
});
return <div>
<h2>Kids Finder</h2>
<Tags onChangeFilter={this.changeFilter}/>
{list}
</div>
}
});
var options = {
Data: [{
name: 'Eric Cartman',
tags: ['male', 'child']
},{
name: 'Wendy Testaburger',
tags: ['female', 'child']
},{
name: 'Randy Marsh',
tags: ['male']
},{
name: 'Butters Stotch',
tags: ['male', 'blonde', 'child']
},{
name: 'Bebe Stevens',
tags: ['female', 'blonde', 'child']
}]
};
var element = React.createElement(List, options);
React.render(element, document.body);
How do I make it better?
It is simple.
take a look at this
https://codepen.io/anon/pen/mepogj?editors=001
basically you want to deal with states of your component so you check the currently active one. you will need to include
getInitialState: function(){}
//and
isActive: function(){}
check out the code on the link
this is pretty useful:
https://github.com/JedWatson/classnames
You can do stuff like
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
or use it like this
var btnClass = classNames('btn', this.props.className, {
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
Taken from their site.
render() {
let className = 'menu';
if (this.props.isActive) {
className += ' menu-active';
}
return <span className={className}>Menu</span>
}
https://reactjs.org/docs/faq-styling.html
Since you already have <Tags> component calling a function on its parent, you do not need additional state: simply pass the filter to the <Tags> component as a prop, and use this in rendering your buttons. Like so:
Change your render function inside your <Tags> component to:
render: function() {
return <div className = "tags">
<button className = {this._checkActiveBtn('')} onClick = {this.setFilter.bind(this, '')}>All</button>
<button className = {this._checkActiveBtn('male')} onClick = {this.setFilter.bind(this, 'male')}>male</button>
<button className = {this._checkActiveBtn('female')} onClick = {this.setFilter.bind(this, 'female')}>female</button>
<button className = {this._checkActiveBtn('blonde')} onClick = {this.setFilter.bind(this, 'blonde')}>blonde</button>
</div>
},
And add a function inside <Tags>:
_checkActiveBtn: function(filterName) {
return (filterName == this.props.activeFilter) ? "btn active" : "btn";
}
And inside your <List> component, pass the filter state to the <tags> component as a prop:
return <div>
<h2>Kids Finder</h2>
<Tags filter = {this.state.filter} onChangeFilter = {this.changeFilter} />
{list}
</div>
Then it should work as intended. Codepen here (hope the link works)
you can also use pure js to accomplish this like the old ways with jquery
try this if you want a simple way
warning: this may not be the correct way to do it in react.
document.getElementById("myID").classList.add("show-example");
const activeState = (e)=>{
var id = e.target.id
const idArray = ["homeBtn","aboutBtn","servicesBtn","portfolioBtn","testmBtn","blogBtn","contactBtn"]
idArray.forEach((element)=> {
document.getElementById(element).classList.remove("active")
});
console.log(id);
document.getElementById(id).classList.add("active")
}

Categories