i'm using Meteor/ReactJS and i'm building a TodoList with lists. When a user click on a list item, I display the tasks associated to this list.
I can get the items, I'm calling the onClick with a parent function.
The problem is I would like to add an "active" class to the list item I just clicked. But I don't know how to do it on the parent class or neither in the child..
Here is the parent class (i'm calling this function in the render():
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map((list) => (
<List
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
/>
));
}
and here is the child:
render() {
return (
<ListGroupItem onClick={this.props.selectList}>
{this.props.list.name}
<span className="pushRight">
<Glyphicon
onClick={() => this.deleteThisList()}
glyph="glyphicon glyphicon-remove"
/>
</span>
</ListGroupItem>
);
}
How could I get the result of selectList in parent and then give the class to the child, or How could I handle the click in the child and give the class directly in the child ?
Thanks a lot for help :)
Parent
1.pass selected id to List comp
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map(list => (
<List
selectedItemId={this.state.listSelected}
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
/>
));
}
Child
2.Compare list.id with passed selectedItemId, if true, so this item is active
render() {
const { selectList, list, selectedItemId} = this.props;
return (
<ListGroupItem isActive={list.id==selectedItemId} onClick={selectList}>
{list.name}
<span className="pushRight">
<Glyphicon
onClick={() => this.deleteThisList()}
glyph="glyphicon glyphicon-remove"
/>
</span>
</ListGroupItem>
);
}
3.then in ListGroupItem
render(){
const {isActive}=this.props;
return (
<div className={"someclass "+(isActive? 'active':'')}>
.......
</div>
);
}
Related
I have a small restaurant app which lets users order from a menu. The menu has three types: food, drink, dessert. The component structure from top to bottom is Order -> Menu -> MenuItem. I want to separate the menu page based on type (example: all food items are under a title called FOOD and so on). Right now, the Menu component receives the menu array as a prop from Order and renders MenuItem for each item in the array. Each item has a property called type. I omitted certain parts of the code unrelated to this issue for brevity.
//Order
export default function Order() {
const [menu, setMenu] = useState<Array<{}>>([]);
const [total, setTotal] = useState(0);
useEffect(() => {
apiFetch("menu").then((json) => setMenu(json.menu));
}, []);
async function handleSubmit(e: any) {
e.preventDefault();
const selectedItems = getSelectedItems(TotalStore);
apiFetch("order", "post", { selectedItems })
.then((json) => {
alert("Order has been submitted");
setTotal(0);
TotalStore.reset();
localStorage.setItem("last_order_id", json.order.id);
function checkOrderStatus() {
apiFetch(
`order/${json.order.id || localStorage.getItem("last_order_id")}`
).then((placedOrder) => {
const { order } = placedOrder;
if (order[0].status === 2) {
alert("Your order is ready!");
} else {
setTimeout(checkOrderStatus, 5000);
}
});
}
checkOrderStatus();
})
.catch((error) => {
alert("Server error");
});
}
function orderPlaced(total: number) {
return total !== 0 ? true : false;
}
return (
<div>
{menu.length > 0 ? (
<>
<div className="menu">
<div className="menu-title">Food Menu</div>
<form id="menu-form" onSubmit={handleSubmit} autoComplete="off">
<Menu onChange={itemChanged} props={menu} />
<button type="submit" disabled={!orderPlaced(total)}>
Place Order
</button>
</form>
</div>
<div className="order-total">
<h2>
Total: $<span>{total.toFixed(2)}</span>
</h2>
</div>
</>
) : (
<>Loading Menu</>
)}
</div>
);
}
//Menu
export default function Menu({ onChange, props }: MenuProps) {
return (
<div>
{props.map((food: any, index: number) => {
return (
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
);
})}
</div>
);
}
//MenuItem
export default function MenuItem({ onChange, item, type }: MenuItemProps) {
return (
<div>
<article className="menu-item" data-item-type={type}>
<h3 className="item-name">{item.name}</h3>
<input
type="number"
className="menu-item-count"
min="0"
value={data.count}
onChange={menuItemCountChange}
/>
<strong className="item-price">${item.price.toFixed(2)}</strong>
</article>
</div>
);
}
Here is what the page currently looks like:
You want to group your menu data by the food.type property. One way would be to sort the menu items into their food type "category, then rendering each group separately.
export default function Menu({ onChange, items }) {
const foodCategories = items.reduce((categories, item) => {
if (!categories[item.type]) {
categories[item.type] = []; // <-- new array for category type
}
categories[item.type].push(item); // <-- push item into category type
return categories;
}, {});
return (
<div>
{Object.entries(foodCategories).map(([type, foodItems]) => (
<div key={type}>
<h1>{type}</h1> // <-- category header
{foodItems.map((food, index) => ( // <-- map food items
<MenuItem
key={index}
onChange={onChange}
type={food.type}
item={food}
/>
))}
<div>
))}
</div>
);
}
I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};
I have an array of sections in state which holds some list of JSX elements. I'm trying to delete a specific element which is JSX from array when clicked on button but I can't figure out how to do this. Any help would be appreciated.
Section.js
class Section extends React.Component {
state = {
sections: []
};
addLectureHandler = () => {
const secs = this.state.sections;
secs.push(
<LectureItem key={Math.random} removeLectureHandler={() => this.removeLectureHandler(<LectureItem />)} />
);
this.setState({ sections: secs });
};
removeLectureHandler = (item) => {
console.log(item);
};
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<h4 className={classes.sectionTitle}>
Section 1 - <AssignmentIcon /> Introduction
</h4>
<div className={classes.addButtonContainer}>
<Button variant="outlined" className={classes.addButton} onClick={this.addLectureHandler}>
Create Lecture
</Button>
</div>
<div className={classes.lectureContainer}>{this.state.sections}</div>
</div>
);
}
}
export default withStyles(styles)(Section);
LectureItem.js
class LectureItem extends React.Component {
render() {
const { classes, removeLectureHandler } = this.props;
return (
<div className={classes.container}>
<div className={classes.titleContainer}>
<TextField className={classes.textField} label="New Lecture Title" name="lecture" margin="normal" />
<Button className={classes.removeButton} onClick={removeLectureHandler}>
<ClearIcon />
</Button>
</div>
<Button variant="contained" color="primary" className={classes.button}>
Add
</Button>
</div>
);
}
}
export default withStyles(styles)(LectureItem);
Its quite straight forward, its more of a logic statement rather than react, Let me show it to you
For you method addLectureHandler
change push command to the following
secs.push(
<LectureItem key={Math.random} removeLectureHandler={() => this.removeLectureHandler(secs.length)} />
);
You were passing <LectureItem /> which is new element not the one you are pushing, so We will pass length (the actual new index of this element).
Next change you removeLectureHandler to this
removeLectureHandler = (indexToDelete) => {
console.log(indexToDelete);
const secs = this.state.sections;
secs.splice(indexToDelete, 1);
this.setState({ sections: secs });
};
And you have successfully deleted an element from the Sections and UI :-0
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910
Im looping over an object and displaying li tags and attaching a click handler.
Below that is a game component that displays further information.
Can someone help me with my logic and how to tell React to only add the class to the specific component related to the click.
at
Currently when I click one element, all of the following game components open at the same time. I just want the one below to open.
Library Component
class Library extends React.Component {
state = {
active: false
};
toggleClass = index => {
let active = this.state.active;
active = !active;
this.setState({ active });
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={this.props.games[index]}
active={this.state.active}
/>
</Fragment>
))}
</ul>
</section>
);
}
}
Game Component
class Game extends React.Component {
render() {
return (
<div
key={this.props.index}
className={this.props.active ? "show" : "hide"}
>
<p>{this.props.game.name}</p>
<p>{this.props.game.gameId}</p>
<a>Close</a>
</div>
);
}
}
Instead of having a boolean active that you use for each game, you could use an object with a key for each index that indicates if that particular game is active.
Example
class Library extends React.Component {
state = {
active: {}
};
toggleClass = index => {
this.setState(previousState => {
const active = { ...previousState.active };
active[index] = !active[index];
return { active };
});
};
render() {
return (
<section>
<h2>Game Library</h2>
<ul>
{this.props.games.map((game, index) => (
<Fragment key={`${index}-${game.name}`}>
<li
key={`${index}-${game.gameId}`}
onClick={() => this.toggleClass(index)}
>
{game.name}
</li>
<Game
key={index}
index={index}
game={game}
active={this.state.active[index]}
/>
</Fragment>
))}
</ul>
</section>
);
}
}