React.js modify child element on parent click - javascript

I have a following react component:
<li key={contact.id} class="option contact-item">
<div class="thumbnail">
<a><span>{contact.name.slice(0, 1)}</span></a>
</div>
<div class="text">
<div class="label">
<div class="name">{contact.name}</div>
<div class="status">{contact.status}</div>
</div>
<div class="select-container">
<div class="select">
<i class="icon-check"></i>
</div>
</div>
</div>
</li>
I need to toggle the color of <i class="icon-check"></i> when clicking the whole <li>
How can I do that?

Firstly, in react, you don't use class, you use className.
See this for full code: https://codepen.io/pen?editors=0010
Using state, you can change styles/classes/etc
_handleClick(key) {
let clicked = this.state.myList.filter(f => f.id === key)[0];
this.setState({ clicked: clicked });
}
_changeColor(key) {
if (this.state.clicked.id === key) {
return 'icon-checked';
}
else
return 'icon-check';
}
_renderList() {
return this.state.myList.map(contact => (
<li key={contact.id}
onClick={() => this._handleClick(contact.id)}
className="option contact-item">
<i className={this._changeColor(contact.id)}></i>
{contact.name}
</li>
));
}
render() {
return (
<div>
<h1>Hello</h1>
<ul>
{this._renderList()}
</ul>
</div>
);
}

You can set "changeColor" state on <li> click and your <i> can handle this state. If you change component state React will rerender your component.
<li onClick={this.changeColor()} key={contact.id} className="option contact-item">
<div className="thumbnail">
<a><span>{contact.name.slice(0, 1)}</span></a>
</div>
<div className="text">
<div className="label">
<div className="name">{contact.name}</div>
<div className="status">{contact.status}</div>
</div>
<div className="select-container">
<div className="select">
<i className="icon-check" style={if (this.state.colorChanged) {color: 'red'}}></i>
</div>
</div>
</div>
changeColor() {
this.setState({colorChanged: true});
}

I made a simple example for you, it works with a sinle list item Check this out.
If you have a list of items, you need to add a one more component for List itself with onToggleListItem method, which will handle the state change. The state of all list items should be store there. In ListItem component you call the onToggleListItem method with a contact id so you can identify which list item was changed.
handleButtonClick() {
this.props.onToggleListItem(this.props.contact.id);
}

Make use of a state and change the state on click of li
var Hello = React.createClass({
getInitialState() {
return {
colorClass: ''
}
},
toggleClass() {
if(this.state.colorClass == '') {
this.setState({colorClass: 'someColor'});
} else {
this.setState({colorClass: ''});
}
},
render(){
return (
<div>
<li onClick={this.toggleClass}>
Some text
<i className={"icon-check " + this.state.colorClass}>value</i>
</li>
</div>
);
}
})
ReactDOM.render(<Hello />, document.getElementById('app'));
.someColor {
color: yellow;
}
<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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>

Related

Reactjs handleclick binding in a nested map not working

I am writing a simple blog component in React. The component get the post from a second file and renders the content and the hashtags with map (in particular two nested map). I was trying to create a part in which the hashtag are highlighted when clicked, and hence I was following the snippet of the answer to this question. The fact is that it is not working and in particular what it is not working is the binding through bind (checked with console.log output)
class Blog extends React.Component {
state= {
open: {}
}
handleClick = (k) => {
let linkOpenState = true;
if (this.state.open.hasOwnProperty(k)) {
linkOpenState = !this.state.open[k];
}
this.setState({ open: { [k]: linkOpenState } })
}
render(){
return(
posts.map(i=>(
<div class="box">
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span id={k} onClick={this.handleClick.bind(this,k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
)
}
}
export default Blog
Anyone has an idea of what is wrong there? Is it the nested map a problem for the bind?
React expects a single element as a return. You can solve this by wrapping the return in a fragment as <React.Fragment> or <>.
Also, you don't need to bind an arrow function(handleClick when mapping i.hash).
render(){
return(
<>
posts.map(i=>(
<div class="box">
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span key={k} id={k} onClick={() => this.handleClick(k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
</>)}}
You don't need to bind the arrow function.
Also, you need to pass unique key to elements when used inside map, you can use index as a key but if you are mutating the array then use some id or hash or anything which will be unique.
class Blog extends React.Component {
state= {
open: {}
}
handleClick = (k) => {
let linkOpenState = false;
if (this.state.open.hasOwnProperty(k)) {
linkOpenState = this.state.open[k];
}
this.setState({ open: { [k]: linkOpenState } })
}
render(){
return(
posts.map((i, index)=>(
<div class="box" key={i.id || index}>
<article class="media">
<div class="media-content">
<div class="content">
<h1 class="title">{i.title}</h1>
<p>
{i.content}
</p>
</div>
<div class="tags">
{i.hash.map(k=>(<span key={k} id={k} onClick={() => this.handleClick(k)} class={this.state.open[k]? "tag is-primary" : "tag"} >{k}</span>))}
</div>
</div>
</article>
</div>))
)
}
}
export default Blog

hide a div in react when there is no anchor tag in it

I have a react code (just a snippet, its not a complete code) as shown below which shows the list of programs on the webpage. Line A in the react code below renders all list of program on the webpage.
react code:
const renderPrograms = () => {
return programs.map((program, index)=>{
return (
<a href={program.url} key={index}>
<div className="program" >
<div class="hello-world">{program.name}</div>
</div >
</a>
)
})
}
return(
<div class="parent-div">
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
<h5>Programs</h5>
{programs && renderPrograms()} {/*Line A*/}
</div>
</div>
)
The above react code renders the following html code at runtime:
<div class="parent-div">
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div class="abc-def">
<h5>Programs</h5>
<a href="https://www.google.com/">
<div class="program">
<div class="hello-world">TYUV</div>
</div>
</a>
<a href="https://www.twitter.com/">
<div class="program">
<div class="hello-world">SGHS</div>
</div>
</a>
</div>
</div>
Problem Statement:
When Line A does not render anything, my div (<div className ="abc-def">) will look like this at run time:
<div class="abc-def">
<h5>Programs</h5>
</div>
I am wondering what changes I need to make in my react code above so that when Line A doesn't render anything then <div class="abc-def"><h5>Programs</h5></div> should not display on the webpage.
They should be part of the condition:
return(
programs.length > 0 && <div className ="abc-def">
<h5>Programs</h5>
{renderPrograms()}
</div>
)
I changed the condition to check for length, otherwise you'll get a 0 instead of nothing, when empty
Try change this lines:
<div className ="abc-def">
<h5>Programs</h5>
{programs && renderPrograms()} {/*Line A*/}
</div>
into this:
{ programs && (
<div className ="abc-def">
<h5>Programs</h5>
{renderPrograms()} {/*Line A*/}
</div>
) }
Now without programs nothing is displayed.
You can try adding a className like this:
<div className={`abc-def ${programs.length ? "hidden" : ""}`}
Or from the parent componet, you can choose to not render this component with <div>
Instead of trying to hide it using CSS, you also can choose to not render it at all by returning nothing from your function
const RenderPrograms = ({ programs = [] }) => {
if (!programs.length) {
return []
}
return (
<div className="abc-def">
<h5>Programs</h5>
{programs.map((program, index) =>
<a href={program.url} key={index}>
<div className="program">
<div>{program.name}</div>
</div>
</a>
)}
</div>
)
}
ReactDOM.render(
<RenderPrograms />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can try putting your class in a template string and using a ternary operator to display a certain class when some state value is true.
<div className={`${stateValue ? "name-of-class-with-hide" : "abc-def"}`}>
Here is a complete solution. All you need to do is move the <h5>Programs</h5> inside the condition and modify the condition slightly as shown in following snippet.
<div className ="abc-def">
{
programs.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs}/>
</div>
)
}
</div>
Note that I have modified the RenderPrograms function as well to accept arguments.
Following is a full snippet.
function RenderPrograms(props) {
return props.programs.map((program, index)=>{
return (
<a href={program.url} key={index}>
<div className="program" >
<div className="hello-world">{program.name}</div>
</div >
</a>
)
});
}
function MyApp() {
const programs=[
{name:'program 1', url: 'https://url1.com'},
{name:'program 2', url: 'url2'},
{name:'program 3', url: 'url3'},
];
const programs1 = []; // empty program list
return(
<div class="parent-div">
<h1>When programs list is not empty</h1>
{/*Above line is just for explanation -- you may remove it*/}
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
{
programs.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs}/>
</div>
)
}
</div>
{/*Below code is just for explanation -- you may remove it*/}
<hr />
<h1>When programs list is empty</h1>
<div className ="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className ="abc-def">
{
programs1.length > 0 && (
<div>
<h5>Programs</h5>
<RenderPrograms programs = {programs1}/>
</div>
)
}
</div>
</div>
)
}
ReactDOM.render(
<MyApp />,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You need to move rendering of programs into RenderPrograms component. Make RenderPrograms component purelu functional and pass data to it via props (Instead of using function).
const RenderPrograms = ({ programs }) => {
let disp = programs.map((program, index) => {
return (
<a href={program.url} key={index}>
<div className="program">
<div class="hello-world">{program.name}</div>
</div>
</a>
);
});
return disp || <span />;
};
then use RenderPrograms to render programs in main component (One with logis for loading programs).
// let programs = [
// {
// url: "test1.com",
// name: "test1"
// },
// {
// url: "test2.com",
// name: "test2"
// }
// ];
let programs = [];
return (
<div className="App">
<div class="parent-div">
<div className="pqr-xyz">
<h5>Hello World</h5>
</div>
<div className="abc-def">
<h5>Programs</h5>
<RenderPrograms programs={programs} />
{/* {programs && renderPrograms()} Line A */}
</div>
</div>
</div>
);
You can play around with this sandbox

Using odd/even switch for classNames

I want each this.state.title to align according to a different classname.
I tried using css flex boxes/nth-of-type/nth-child, but it did not play nicely with React.
I'm using this.state to get my objects.
My unsuccessful attempt
render: function () {
let className
var newVar = !someVar;
switch(someVar) {
case odd:
className= "post-1 line";
break;
case even:
className = "post-2 right-align line";
break;
}
return (
<article class={I WANT THIS TO FILL FROM SWITCH}>
<div class="s-12 l-6 post-image">
<a href="post-1.html">
<img src="/post1.jpg">
</a>
</div>
<div class="s-12 l-5 post-text">
<a href="#">
<h2>{this.state.title}</h2>
</a>
<p>Testing
</p>
</div>
<div class="s-12 l-1 post-date">
<p class="date">28</p>
<p class="month">feb</p>
</div>
</article>
);
}
});
react rewrite class attribute as className,see react for more details.
render: function () {
let className = ['post-2 right-align line', 'post-1 line'][someVar % 2];
return (
<article className={className}>
<div className="s-12 l-6 post-image">
<a href="post-1.html">
<img src="/post1.jpg"/>
</a>
</div>
<div className="s-12 l-5 post-text">
<a href="#">
<h2>{this.state.title}</h2>
</a>
<p>Testing
</p>
</div>
<div className="s-12 l-1 post-date">
<p className="date">28</p>
<p className="month">feb</p>
</div>
</article>
);
}
The 'someVar' is local variable ? You can try write it in state, Via this.setState mutate it for your need.
here is the whole example like this:
class Item extends Component {
render() {
let {index, title}=this.props;
let className = ['post-2 right-align line', 'post-1 line'][index % 2];
return <li key={index} id={index} className={className}>{title}</li>
}
}
class TodoList extends Component {
render() {
let i = 0;
let itemFactory = (props = {}) => {
return <Item key={i} index={i++} {...props}/>
};
return (<ul>
{this.props.items.map((item) => itemFactory({title: item}))}
</ul>);
}
}
ReactDOM.render(
<TodoList items={['first', 'second', 'last']}/>,
document.getElementById('container')
);

Toggling CSS as a state for li items in React

I've a list of photos being displayed as a React Component.
The individual list items are initially displayed with a + sign. The behavior I'm trying to acheive is on clicking a particular list item, the sign changes to -, and once a different list item is clicked, the first one reverts to + and the current one goes to -.
This is my render code,
render() {
let classes = "glyphicon add-icon " + (this.state.glyphClass ? "glyphicon-plus" : "glyphicon-minus");
return (
<div className="row">
<ul className="list-inline">
{this.props.images.map(function (image) {
return (<li key={image.id}>
<a href="#" onClick={this.getMediaId} data-id={image.id} data-class={image.src} data-owner={image.owner}>
<div className="img-wrapper">
<div className="img" style={{backgroundImage: `url(${image.src})`}}></div>
<div className="img-selector">
<span className={classes} id="plus-icon" aria-hidden="true"></span>
</div>
</div>
</a>
</li>);
}, this)}
</ul>
</div>
);
}
This is the constructor,
constructor(props){
super(props);
this.getMediaId = this.getMediaId.bind(this);
this.state = { glyphClass : true };
}
And this is the method that does the toggle,
getMediaId(event){
event.preventDefault();
this.setState({
glyphClass: !this.state.glyphClass
});
console.log(this.state.glyphClass);
....
}
The behavior that I'm getting now is that onClick on any list item all the list items are toggling to - and then on a subsequent click all are toggling to +. I'd really appreciate some help in fixing this.
You can have a selectedItem in the state instead.
constructor(props){
super(props);
this.getMediaId = this.getMediaId.bind(this);
this.state = { selectedItem : null };
}
Then in the get media set the id of selectedItem when clicked.
getMediaId(id){
this.setState({
selectedItem: id
});
}
Then you can check the id when rendering the list.
if the selectedItem has the same id of the list, render the - else render +.
render() {
return (
<div className="row">
<ul className="list-inline">
{this.props.images.map(function (image) {
const classes = this.state.selectedItem === image.id ? 'glyphicon add-icon glyphicon-minus' : 'glyphicon add-icon glyphicon-plus';
return (<li key={image.id}>
<a href="#" onClick={(event) => {event.preventDefault(); this.getMediaId(image.id); }} data-id={image.id} data-class={image.src} data-owner={image.owner}>
<div className="img-wrapper">
<div className="img" style={{backgroundImage: `url(${image.src})`}}></div>
<div className="img-selector">
<span className={classes} id="plus-icon" aria-hidden="true"></span>
</div>
</div>
</a>
</li>);
}, this)}
</ul>
</div>
);
}

attach onClick handler to populated list item reactjs

i have a function that i want to fire when a button is being clicked, this button is included in a list item that gets populated based on values from an array passed from the parent component, but its not working and returning an error saying that the function is undefined, how can i make this work?
import React from 'react';
export default class Card extends React.Component {
constructor(){
super()
this.state = {
index: null
};
}
handleClick(x){
this.setState({
index: x
})
}
render(){
/**
* Populates list items according to data passed
* on to resultsArray.
*/
var items = this.props.resultsArray;
var itemslist = items.map(function(item, index){
return(
<li key={ index } class="card" >
<div class="card-header">
<span class="hour-price"><span>{ item.hourPrice } € /hour</span></span>
<img src={ item.image } class="card-img" />
Book
</div>
<div>
<div class="card-info">
<p class="workplace-name">{ item.name }</p>
<span class="score">{ item.score } ★</span>
<p class="location">{ item.location }</p>
</div>
<div class="card-footer">
<p class="price">{ item.price } € / Day</p>
</div>
</div>
</li>
);})
return(
<div class="results-container">
<ul class="card-list center">
{ itemslist }
</ul>
</div>
);
}
}
You should change your render to the below codes. Use that instead of this.
var items = this.props.resultsArray;
var that = this;
var itemslist = items.map(function(item, index){
return(
<li key={ index } className="card" >
<div className="card-header">
<span className="hour-price"><span>{ item.hourPrice } € /hour</span></span>
<img src={ item.image } className="card-img" />
Book
</div>
......

Categories