React JS add component to specific clicked li - javascript

Can't figure out the way to create the following function. I currently load data (local json) into a li. Upon click I want to add a new component to the clicked li element with data.
Example:
<ul>
<li> 1 </li>
<li> 2 </li>
<li>
3
<div id="data">
<!-- appended data -->
</div>
</li>
</ul>
Upon clicking another li the previous appended element should be removed and added to the newly clicked li. (Row toggle)
If anyone could kick me in the right React direction.
import React, { Component } from 'react';
import Flowers from 'flowers.json';
class Flowers extends React.Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: true
};
this.onClick = this.handleClick.bind(this);
}
handleClick = (e) => {
console.log(this);
console.log(e);
console.log('li item clicked! ' + e.currentTarget);
// appends to clicked div
e.currentTarget.style.backgroundColor = '#ccc';
}
render() {
const List = Flowers.map((flower) =>
<li onClick={this.handleClick.bind(this)} key={flower.id} id={flower.id}>
{flower.name}
</li>
);
return(
<ul>{List}</ul>
);
}
}
class App extends Component {
render() {
return (
<Flowers />
);
}
}
export default App;

You could just add to your state the ID of the flower that is active.
E.g.
this.state = { ActiveFlowerID: null }
So when no ActiveFlower none is open.
On handle click method make the change on the state, and render your flowers additional data with an if matching the flower id and the state.
Hope this helps.

Related

How to change the target span onClick

I want a line-through to appear on the span content onClick in React. Have tried creating a state and associating it to the li. But onclick, it affects all the li tags. I want it to only change the style of the particular li tag. I am new to react and trying to learn slowly.
import React from 'react';
class Todos extends React.Component{
constructor(props) {
super(props);
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
const click = this.state.clicked;
console.log(click)
if(click){
this.setState({
clicked: false
})
}
else{
this.setState({
clicked: true
})
}
console.log(this.state.clicked);
}
render() {
return (
<ul className="bg-warning p-4 list-group">
{this.props.todos.length ? (
this.props.todos.map(todo => {
return (
<li className="list-group-item" key={todo.id}>
<span className="pr-2">{todo.content}</span>
<button onClick={()=>{this.props.deleteTodo(todo.id)}}>Delete</button>
</li>
)
})
) : (
<p className="center">You have no todo's left, yay!</p>
)}
</ul>
)
}
}
export default Todos;
#eramit2010 answer makes sense, but I am extending it to fit the requirement that, when clicked on todo item (i.e. span) show line-through and when clicked on delete button simply delete the todo item.
You need to maintain clicked todo's in a state array,
state ={
clickedTodo: []
}
You should have separate click event on your span where you want to show line-through,
<span
className="pr-2"
style={{textDecoration: this.state.clickedTodo.includes(index) ? 'line-through' : 'none', paddingRight: '20px', cursor:'pointer'}}
onClick={() => this.lineThrought(todo.id)}
>
{todo.content}
</span>
Here, I have applied CSS to make span line-through when it gets clicked.
Your lineThrought method should be,
lineThrought = (id) => {
if(this.state.clickedTodo.includes(id)){
//To remove line-through
this.setState({clickedTodo: this.state.clickedTodo.filter(item => item !== id)})
}else{
//To add line-through
this.setState({clickedTodo: [...this.state.clickedTodo, id]})
}
}
Demo
you can store deleted ids in local state and then if that perticular id is present in state, you can use custom class for that.
consturctor() {
this.state = {
deleted: []
};
}
render() {
return (
<ul className="bg-warning p-4 list-group">
{this.props.todos.length ? (
this.props.todos.map(todo => {
return (
<li className=`list-group-item ${this.state.deleted.includes(todo.id) ? "list-group-item-deleted" : ""}` key={todo.id}>
<span className="pr-2">{todo.content}</span>
<button onClick={()=> {
this.props.deleteTodo(todo.id);
this.setState(prevState => { deleted: [...prevState.deleted, todo.id]);
}>Delete</button>
</li>
)
})
) : (
<p className="center">You have no todo's left, yay!</p>
)}
</ul>
)
}
in your css file:
.list-group-item-deleted {
text-decoration: line-through
}
If you are already storing deleted ids in redux or somewhere else you can use same.
When someone click in li then it will perform deleteTodo, you said you want it to only change the style of the particular li tag, you can easily perform that using below code inside of you componentdidmount() function
$(document).on('click', 'your class name' , function()
{ $(this).css('color','red');
});
Otherwise, you have to use dynamic state, based on unique id, when someone click event then use that id as a state and change those li color.

Separate onClick for the component as a whole and a button within the component

So I am in the process of creating a todo list written in React. I have a functional component ListItem which renders an <li> element with the task and a trash button.
My current functionality: When the user clicks on the actual text, the text gets struck out, and when they click on the trash button the ListItem gets deleted.
function ListItem(props) {
return (
<li class="list-item"}>
<span class="item-name font" onClick={props.handleItem}>
{props.value}
</span>
<button id="trash-button" onClick={props.handleTrash}>
Trash
</button>
</li>
);
}
What I want my functionality to be: When the user clicks anywhere in the <li> item (excluding the trash button) the text is struck out, and when they click on the trash button the ListItem gets deleted.
I thought I could achieve the above by shifting the handleItem onClick to the <li> item.
function ListItem(props) {
return (
<li class="list-item" onClick={props.handleItem}>
<span class="item-name font">
{props.value}
</span>
<button id="trash-button" onClick={props.handleTrash}>
Trash
</button>
</li>
);
}
However what ends up happening is that even when the trash button is clicked, the handleItem function gets called along with the handleTrash function. Is there a way I can suppress the handleItem function when handleTrash is called?
Thanks in advance!
Edit: My entire code for the Todo List. The form that takes in the task is called AddEntry and that's separate from everything else so I'm not including it.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import AddEntry from './AddEntry'
function ListItem(props) {
return (
<li class="list-item" onClick={props.handleItem}>
<span class="item-name font">
{props.value}
</span>
<button id="trash-button" onClick={props.handleTrash}>
Trash
</button>
</li>
);
}
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {
entries: []
};
this.handleItem = this.handleItem.bind(this);
this.handleTrash = this.handleTrash.bind(this);
this.handleAdd = this.handleAdd.bind(this);
}
renderListItem(e) {
if(e.struck) {
return (
<ListItem
key={e.key}
value={<strike>{e.item}</strike>}
handleTrash={() => this.handleTrash(e)}
handleItem={() => this.handleItem(e)}
/>
);
}
else {
return(
<ListItem
key={e.key}
value={e.item}
handleTrash={() => this.handleTrash(e)}
handleItem={() => this.handleItem(e)}
/>
);
}
}
handleAdd(e) {
const entries = this.state.entries;
entries.push({item: e, struck: false, key: entries.length});
this.setState({
entries: entries
});
}
handleTrash(e) {
const entries = this.state.entries;
for(let i = 0; i < entries.length; i++) {
if(entries[i] === e) {
entries.splice(i, 1);
}
}
this.setState({
entries: entries
});
}
handleItem(e) {
console.log("hi");
const entries = this.state.entries;
let index = entries.findIndex(ele => e.key === ele.key);
entries[index].struck = !entries[index].struck;
this.setState({
entries: entries
});
}
render() {
return (
<div class="todo-list">
<AddEntry handleAdd={this.handleAdd}/>
<ul>{this.state.entries.map((e) => this.renderListItem(e))}</ul>
</div>
);
}
}
ReactDOM.render(
<TodoList />,
document.getElementById('root')
);
this is how it's working with the onClick in the li element
keep the onClick on li itself and in your handleTrash function use preventPropogation to stop propogation.
handleTrash(event){
event.stopPropagation();
// code the rest
}
here is a working example https://codesandbox.io/s/1vqwo4j0yl

Create and return a new component on-click in parent component

I have two components Class and Students. The Class component renders and returns a list of Classes in <li>. I want to add click events to display the Students for each Class in Class component.
I have the following in the render method of the Class component:
render(){
const renderClasses = () =>
this.props.classes.map(class => {
return (
<li>
{class.name}
//class object also has a property of 'students'
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
I want to be able to click on the anchor tags and display the corresponding students for that class. Of course the Student component should receive a prop as follows:
<Students students={this.class.students} />
Thanks in advance!
You can keep a component state to save the class index that should show its students, and then add an onClick handler on the anchor to change that index.
Try the code below:
export default class Test extends Component {
constructor(props)
{
super(props);
this.state = {activeClassIndex : -1}
this.setActiveClassIndex = this.setActiveClassIndex.bind(this);
}
setActiveClassIndex(index){
this.setState({
activeClassIndex : index
})
}
render(){
const renderClasses = () =>
this.props.classes.map( ( currentClass , index ) => {
return (
<li>
<a href="#" onClick={ () => { this.setActiveClassIndex(index) } }>{currentClass.name}</a>
{this.state.activeClassIndex == index ? <Students students={currentClass.students} /> : "" }
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
}

Need Change active class in tab ( react)

If I click Second page,after reload page, all tabs get class CURRENT, How to fix this? How to disable current class on first TAB ?
If i remove activeClassName="current", After Reloading current class switch to first tab, but I saw second tab content
import React from 'react'
import { Link, browserHistory,IndexLink } from 'react-router'
class Tabs extends React.Component{
constructor(props) {
super(props);
this.state = {
index: ''
};
this.onclick = this.onclick.bind(this);
}
onclick(index) {
this.setState({index});
}
getListItem(){
let numbers = this.props.menuitems;
let listItems = numbers.map((item,index) =>
<li
onClick={this.onclick.bind(this, index)} key={index}>
<Link to={item.link} activeClassName="current"
className={index == this.state.index? "tab-link current" : "tab-link"}>{item.linkName}</Link>
</li>
);
return listItems;
}
render() {
return (
<div>
<ul className="tabs" >{this.getListItem()}</ul>
<div className="tabs-header-stripe"></div>
</div>
);
}
}
export default Tabs
According to the scenario u described, you need a stateful component instead of stateless function component. Store the index of current tab in state variable and update it inside onclick method, during the rendering compare the index of state variable with the index of item, if they are same then apply the class. Try this a similar example, it should work in ur case also:
class HelloWidget extends React.Component{
constructor(props) {
super(props);
this.state = {
index: ''
};
this.onclick = this.onclick.bind(this);
}
onclick(index) {
this.setState({index});
}
getListItem(){
let numbers = this.props.menuitems;
let listItems = numbers.map((item,index) =>
<li style={{color: this.state.index==index?'red': 'black'}} className={this.state.index == index ? "tab-link current" : "tab-link"} onClick={this.onclick.bind(this, index)} key={index}>{index}-{item.number}</li>
);
return listItems;
}
render() {
return (
<div>
<ul className="tabs" >{this.getListItem()}</ul>
<div className="tabs-header-stripe"></div>
</div>
);
}
}
React.render(<HelloWidget menuitems={[{number:0, index:0}, {number:1, index:1}, {number:3, index:3}]}/>, document.getElementById('container'));
check the jsfiddle for working example: https://jsfiddle.net/27po3p4b/
the className current is only on the first tab because you are checking if index === 0 (first tab) and if true - you are adding the current class.
You need to keep a state of activeTabIndex and on your onClick function change the activeTabIndex to the right index.
and then you can check
className={index === this.state.activeTabIndex ? "tab-link current" : "tab-link"}

e.target is accessible, but e.target.value is not in React component

I'm having a strange issue with my React app where I can't get my deleteTime() function to work. I was going to try to remove an element from this.state.times by using e.target.value which would be the {key} for the <li> I want to remove. The value attribute is getting correctly added to the element, but I just can't access it. I know for a fact that the problem has to do with MaterializeCSS because if I change the element from an <i> to a <button> without the icons stuff, the code works.
There are basically two components, the main App which gives all the props to the RecentTimes component which just displays a list of times that are formatted like this: 00 : 00 . 00
Here is what the App component looks like (I removed all the irrelevant stuff):
class App extends React.Component {
constructor() {
super();
this.state = {
times: []
};
}
deleteTime(e) {
console.log(e.target); // <i value="1" class="material-icons right" data-reactid=".0.0.2.0.0.2:$1.1">close</i>
console.log(e.target.value); // undefined
}
render() {
return (
<RecentTimes
times={this.state.times}
deleteTime={this.deleteTime}
/>
);
}
}
I have no idea why e.target.value is undefined if e.target clearly has a value attribute.
And here is the component for the RecentTimes:
class RecentTimes extends React.Component {
render() {
let icons = 'material-icons right';
let times = this.props.times.map((time, timeIndex) => {
return (
<li key={timeIndex}>
{time}
<i value={timeIndex} onClick={this.props.deleteTime} className={icons}>close</i>
</li>
);
});
return (
<ul>{times}</ul>
);
}
}
Use a data attribute
<i data-value={timeIndex} onClick={this.props.deleteTime} className={icons}>close</i>
and
e.target.getAttribute('data-value');
or if the browser supports dataset
e.target.dataset.value
You need to use e.target.id.
class RecentTimes extends React.Component {
render() {
let icons = 'material-icons right';
let times = this.props.times.map((time, timeIndex) => {
return (
<li key={timeIndex}>
{time}
<i id={timeIndex} onClick={this.props.deleteTime} className={icons}>close</i>
</li>
);
});
return (
<ul>{times}</ul>
);
}
}

Categories

Resources