I would like to change current li item color when I click it.
How to add prop to item(using array map), when I click it? I use styled-components
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: []
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index}>
{item}
<button onClick={() => this.deleteItemHandler(index)}> Delete</button>
</Li>
);
});
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
}
}
Check out this code working on CodeSandBox
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import styled from "styled-components";
const Li = styled.li`
color: ${props => (props.checked ? "red" : "green")};
`;
class App extends Component {
state = {
value: "",
items: [],
selected: -1
};
handleChange = e => {
this.setState({
[e.currentTarget.name]: e.currentTarget.value
});
};
handleAdd = () => {
const { items, value } = this.state;
this.setState({
items: [...items, value],
value: ""
});
};
handleRemove = index => {
const { items, selected } = this.state;
items.splice(index, 1);
if (index < selected) index = selected - 1;
if (index === selected) index = -1;
if (index > selected) index = selected;
this.setState({
items: items,
selected: index
});
};
handleActiveItem = index => {
this.setState({ selected: index });
};
render() {
const { value, items, selected } = this.state;
return (
<div>
<input
type="text"
value={value}
name="value"
onChange={this.handleChange}
/>
<button
style={{ margin: "0px 5px" }}
disabled={!value}
className="btn btn-sm btn-success"
onClick={this.handleAdd}
>
+
</button>
<ul className="li">
{items.map((item, index) => (
<Li key={index} checked={selected === index}>
<span onClick={() => this.handleActiveItem(index)}>{item}</span>
<button
style={{ margin: "1px 5px" }}
className="btn btn-sm btn-danger"
onClick={() => this.handleRemove(index)}
>
X
</button>
</Li>
))}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Ignore the handlers if you don't need them. Thanks to this effort I learnt about styled-components and discovered CodeSandBox.
EDIT :
Added a <span> inside <li> to avoid nested onClick, previously <li> (parent) and <button> (child) both had onClick attribute. On button Click two onClick events were fired resulting in unexpected behaviour in some use cases. You must correct this in your code.
Added logic to keep item selected when an item before it is deleted.
Added button validation to avoid adding empty string/items in list.
Also updated CodeSandBox Code to reflect above changes.
So you need keep track of the active index, and use it too change the color of the active component color.
state ={
activeIndex: void 0
}
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
deleteItemHandler = (index) => {
this.setState({
activeIndex: index
})
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.activeIndex} > {item} < button onClick={() => this.deleteItemHandler(index)
}> Delete</button ></Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
Try this
const Li = styled.li`
color: ${props => props.checked ? "red" : "green"};
;`
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
currentChecked: null
};
}
render() {
const ShowItems = this.state.items.map((item, index) => {
return (
<Li key={index} checked={index === this.state.currentChecked} >
{item}
<button onClick={() => this.setState({currentChecked: index})}>Delete</button >
</Li >
)
})
return (
<Wrapper>
<AddItem
addItemHandler={this.addItem}
InputValue={this.state.value}
InputValueHandler={this.inputValue}
/>
{ShowItems}
</Wrapper>
);
Related
i have my react redux project, that i am wanting to add a search to the menuItems Component. but i cant seem to figure out how to search over the existing map. ive created a input with the onChange event that sets the state to the input term. but i cant semm what to do after that.
class MenuItems extends Component {
state = {
searchTerm: '',
}
handleclick = (item) => {
this.props.deleteMenuItem(item.id);
}
handleSearch = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
filteredItem = () => {
let filtered = [...this.props.menuItems].filter(item => item.name === "burger")
}
render(){
let showItems = this.props.menuItems;
return (
<div>
<input
placeholder="search"
name="searchTerm"
type="text"
value={this.state.searchTerm}
onChange={this.handleSearch}
/>
{showItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
</div>
)
}
}
export default connect(null, {deleteMenuItem})(MenuItems)
It looks like you just need to filter with your searchTerm and it will work just fine.
class MenuItems extends Component {
state = {
searchTerm: "",
};
handleclick = (item) => {
this.props.deleteMenuItem(item.id);
};
handleSearch = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
};
render() {
let showItems = this.props.menuItems;
const filtered =
[...this.props.menuItems].filter((item) =>
item.name.includes(this.state.searchTerm)
) ?? [];
return (
<div>
<input
placeholder="search"
name="searchTerm"
type="text"
value={this.state.searchTerm}
onChange={this.handleSearch}
/>
{showItems.map((item) => (
<li className="list" key={item.id}>
{item.name}
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>
delete
</button>
</li>
))}
{filtered.map((item) => (
<li className="list" key={item.id}>
Filtered: {item.name}
<br></br>
</li>
))}
</div>
);
}
}
const App = () => {
return (
<div>
<MenuItems menuItems={[{ name: "name1" }, { name: "name2" }]} />
</div>
);
};
i am trying to make a todo list in reacthook. my App.js:
import React,{useState} from 'react';
import AddTodo from './TodoFiles/AddTodo'
import TodoList from './TodoFiles/TodoList'
const defaultItems=[
{id:1,title:'Write React Todo Project',completed:true},
{id:2,title:'Upload it to github', completed:false}
]
const App=()=>{
const [items,setItems]=useState(defaultItems)
return(
<div style={{width:400}}>
<AddTodo items={items} setItems={setItems}/>
<br/>
<hr/>
<TodoList items={items}/>
<hr/>
</div>
)
}
export default App;
the addTodo.js is:
import React,{useState} from 'react'
const AddTodo=({items,setItems})=>{
const[title,setTitle]=useState('')
const handleTitle=(event)=>{
setTitle(event.target.value)
}
const handleAddTodo=()=>{
const NewItem={title}
setItems([NewItem,...items])
}
return(
<form onSubmit={e=>{e.preventDefault();handleAddTodo()}}>
<input type="text" placeholder="enter new task..." style={{width:350,height:15}}
value={title} onChange={handleTitle}/>
<input type="submit" style={{float:'right', marginTop:2}}/>
</form>
)
}
export default AddTodo
TodoList.js is:
import React, { useState } from "react";
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Complete
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Remove
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
setItems(items.filter((p,index)=>p.index!==index))
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
CompeleteTodo has been resolved but when i press the remove button it is not working and nothing has been deleted. there is no error while executing npm start. developer tools showing a warning:
index.js:1 Warning: Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
in input (at TodoList.js:6)
in div (at TodoList.js:5)
in TodoItem (at TodoList.js:30)
in TodoList (at App.js:18)
in div (at App.js:13)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
what more i can to to fix it?
You don't set index in parameter of your function, simply you can do this:
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Remove
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Complete
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
const newItem = [...items];
newItem.splice(index, 1);
setItems(newItem);
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
and your remove and complete function is upside down :)
Check this
here is my suggestion > implement remove-complete functional with array items id and not index of elements.
please read this Lists and Keys
Here is Demo
App
import React, { useState } from "react";
import AddTodo from "./TodoFiles/AddTodo";
import TodoList from "./TodoFiles/TodoList";
const defaultItems = [
{ id: 1, title: "Write React Todo Project", completed: true },
{ id: 2, title: "Upload it to github", completed: false }
];
const App = () => {
const [items, setItems] = useState(defaultItems);
return (
<div style={{ width: 400 }}>
<AddTodo items={items} setItems={setItems} />
<br />
<hr />
<TodoList items={items} setItems={setItems} />
<hr />
</div>
);
};
export default App;
AddTodo
import React, { useState } from "react";
const AddTodo = ({ items, setItems }) => {
const [title, setTitle] = useState("");
const handleTitle = event => {
setTitle(event.target.value);
};
const handleAddTodo = () => {
const newItem = [
{
id: Math.max(...items.map(x => x.id), 0) + 1
completed: false,
title
},
...items
];
setItems(newItem);
};
return (
<form
onSubmit={e => {
e.preventDefault();
handleAddTodo();
}}
>
<input
type="text"
placeholder="enter new task..."
style={{ width: 350, height: 15 }}
value={title}
onChange={handleTitle}
/>
<input type="submit" style={{ float: "right", marginTop: 2 }} />
</form>
);
};
export default AddTodo;
TodoList
import React from "react";
import TodoItem from "./TodoItem";
const TodoList = ({ items, setItems }) => {
const completeTodo = id => {
setItems(
items.map(item => (item.id === id ? { ...item, completed: true } : item))
);
};
const removeTodo = id => {
setItems(items.filter(p => p.id !== id));
};
return items.map(p => (
<TodoItem
{...p}
key={p.id}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
TodoItem
import React from "react";
const TodoItem = ({ id, title, completed, completeTodo, removeTodo }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} onChange={() => {}} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(id)}>
Complete
</button>
<button style={{ float: "right" }} onClick={() => removeTodo(id)}>
Remove
</button>
</div>
);
};
export default TodoItem;
In the following code I reorganize a list from bottom to top or from top to bottom.
I would rather be able to reorganize it from left to right.
Do you have any idea how to do this?
App
import React, { Component } from "react";
import FruitList from "./FruitList";
const UP = -1;
const DOWN = 1;
class App extends React.Component {
state = {
// set new state for bind key items
items: [
{ id: 1, name: "orange", bgColor: "#f9cb9c" },
{ id: 2, name: "lemon", bgColor: "#fee599" },
{ id: 3, name: "strawberry", bgColor: "#e06666" }
]
};
handleMove = (id, direction) => {
const { items } = this.state;
const position = items.findIndex(i => i.id === id);
if (position < 0) {
throw new Error("Given item not found.");
} else if (
(direction === UP && position === 0) ||
(direction === DOWN && position === items.length - 1)
) {
return; // canot move outside of array
}
const item = items[position]; // save item for later
const newItems = items.filter(i => i.id !== id); // remove item from array
newItems.splice(position + direction, 0, item);
this.setState({ items: newItems });
};
render() {
return <FruitList fruitList={this.state.items} onMove={this.handleMove} />;
}
}
export default App;
My components
import React, { Component } from "react";
const UP = -1;
const DOWN = 1;
class FruitList extends React.Component {
render() {
const { fruitList, onMove } = this.props;
return (
<div>
{fruitList.map(item => (
<div key={item.id} style={{ backgroundColor: item.bgColor }}>
<div className="fruitsId">{item.id}</div>
<div className="fruitsName">{item.name}</div>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, UP)}>▲</a>
<a onClick={() => onMove(item.id, DOWN)}>▼</a>
</div>
</div>
))}
</div>
);
}
}
export default FruitList;
You can do this using css flexbox.
I applied { display: "flex" } to the root div in FruitList. (The direction is default row).
FruitList.js
class FruitList extends React.Component {
render() {
const { fruitList, onMove } = this.props;
return (
<div style={{ display: "flex" }}>
{fruitList.map(item => (
<div
key={item.id}
style={{
backgroundColor: item.bgColor,
display: "flex",
}}
>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, LEFT)}>←</a>
</div>
<div className="fruitsId">{item.id}</div>
<div className="fruitsName">{item.name}</div>
<div className="fruitsArrows">
<a onClick={() => onMove(item.id, RIGHT)}>→</a>
</div>
</div>
))}
</div>
);
}
}
Playground
When I type something in my textarea, and then click on the button, this new element is stocked inside an array and displayed in a list in my react app. I want the array's elements to be crossed when I click on them.
I've written a function to change the state of 'crossed' to its opposite when i click on the element, and then the style of the elements would change depending on whether it's true or false.
app.js:
import React from 'react';
import Tasks from './tasks.js';
import Item from './component.js';
import './App.css';
class App extends React.Component {
state = {
todolist: [],
crossed: false
}
addData(val) {
this.setState({ todolist: this.state.todolist.concat(val) },
() => console.log(this.state.todolist))
}
cross() {
this.setState({ crossed: !this.state.crossed },
() => console.log(this.state.crossed))
}
render() {
return (
<div className="App">
<Tasks onClick={value => this.addData(value)} />
{
(this.state.crossed) ? (<ul>
{this.state.todolist.map((e) => {
return <Item
item={e}
onClick={(e) => this.cross(e)}
style={{ textDecoration : 'line-through' }} />}
)
}
</ul>) : (
<ul>
{this.state.todolist.map((e) => {
return <Item
item={e}
onClick={(e) => this.cross(e)}
/>}
)
}
</ul>
)
}
</div>
);
}
}
export default App;
component.js:
import React from 'react'
class Item extends React.Component{
render(){ return(
<li onClick={this.props.onClick} style={this.props.style}>{this.props.item}
</li>
);
}}
export default Item
tasks.js :
import React from 'react'
class Tasks extends React.Component {
constructor(props) {
super(props)
this.state = {
value: '',
}
}
handleChange = e => {
this.setState({ value: e.target.value })
}
render() {
return (<div>
<textarea value={this.state.value} onChange={this.handleChange} ></textarea>
<button onClick={() => this.props.onClick(this.state.value)}>Add task</button>
</div>)
}
}
export default Tasks
I want each element to be crossed on its own when I click on it, but all the elements get crossed when I click on any one of them.
You should have some key for each object to differentiate,
addData(val) {
const tempObj = {
val: val,
crossed: false
}
this.setState({ todolist: this.state.todolist.concat(tempObj) },
() => console.log(this.state.todolist))
}
Now you will have crossed key for each object. I have not run the code, but this should work.
cross = e => {
e.crossed = !e.crossed;
}
(
<ul>
{this.state.todolist.map(e => {
return <Item
item={e}
onClick={(e) => this.cross(e)}
style={e.crossed && { textDecoration : 'line-through' }} />} // use ternary operator or this kind of && condition here
)
}
</ul>
)
My goal is when I click on any list items, I want to show hidden button as confirm. What I tried is to show button on-click on function name selectItem as follows :
<Col xs={3}>
<ul>
<h2>Your orders </h2>
{selectedItems.map((item, i) => (
<li key={i}>
{item.name} {item.cost} {item.quantity}
<span onClick={() => this.deleteItem(i)}>cancel</span>
</li>
))}
</ul>
{this.selectItem()
? <Button type="button" style={{ display: 'block' }}>Confrim</Button>
: <Button type="button" style={{ display: 'none' }}>Confrim</Button>
}
</Col>
This gives the error as follows
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
The question is how I use the function call to display hidden button and hide when I remove all Items.Thanks.
import React from "react";
import {
Form,
FormGroup,
Row,
FormControl,
Col,
Button,
Label,
Modal,
ButtonToolbar,
Table
} from "react-bootstrap";
const MorningDrinks = [
{
id: "1",
name: "Tea",
cost: 15
},
{
id: "2",
name: "Coffee",
cost: 15
},
{
id: "3",
name: "Milk",
cost: 15
}
];
const ChoclateDrinks = [
{
id: "4",
name: "Smoothie",
cost: 15
},
{
id: "5",
name: "Hot Chocolate",
cost: 15
}
];
class MenuCard extends React.Component {
state = {
selectedItems: []
};
selectItem = item => {
const { counter, selectedItems } = this.state;
const newItem = {
...item,
quantity: 1
};
const el = selectedItems.filter(el => el.id === newItem.id);
if (selectedItems.length === 0) {
this.setState({
selectedItems: selectedItems.concat([newItem])
});
} else {
if (el.length) {
const newSelectedItems = selectedItems.map(item => {
if (item.id === newItem.id) {
item.quantity++;
}
return item;
});
this.setState({
selectedItems: newSelectedItems
});
} else {
this.setState({
selectedItems: selectedItems.concat([newItem])
});
}
}
};
deleteItem(i) {
this.setState({
selectedItems: this.state.selectedItems.filter((item, index) => {
return index !== i;
})
});
}
render() {
const { counter, selectedItems } = this.state;
return (
<div className="container">
<p>
Welcome {this.props.name}! Pick your any Break-fast menu you want
</p>
<Row>
<Col xs={3}>
<ul>
<h2>Morning Drinks </h2>
{MorningDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name} {item.cost}
</li>
))}
</ul>
<ul>
<h2>Chocolate Drinks </h2>
{ChoclateDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name} {item.cost}
</li>
))}
</ul>
</Col>
<Col xs={3}>
<ul>
<h2>Your orders </h2>
{selectedItems.map((item, i) => (
<li key={i}>
{item.name} {item.cost} {item.quantity}
<span onClick={() => this.deleteItem(i)}>cancel</span>
</li>
))}
</ul>
<Button type="button" style={{display: 'none'}}>Confrim</Button>
</Col>
<Col xs={3}>
<ul>
<h3>Total</h3>
{selectedItems.reduce(
(acc, item) => acc + item.cost * item.quantity,
0
)}
</ul>
</Col>
</Row>
</div>
);
}
}
export default MenuCard;
this.selectItem()
Sets state, which can't be done inside a render method. Try to render the button by only reading from state.
{this.state.selectedItems.length > 0 ? ... : ...}