I am trying to introduce unique identifiers for list items instead of using the index but every method I try, I can't seem to get it working in the child. This is the base I am working with. I did install and imported import { v4 as uuidv4 } from 'uuid'; to make it a bit easier
All you have to do is simply put in 'uuidv4()' to generate a random ID
Parent
import React from 'react';
import './App.css';
import ShoppingCartList from './ShoppingCartList'
import { v4 as uuidv4 } from 'uuid';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
shoppingCart: [],
newItem: '',
errorMessage: 'false',
};
this.onRemoveItem = this.onRemoveItem.bind(this);
}
handleChange = (e) => {
this.setState ({ newItem: e.target.value})
}
handleClickAdd = (e) => {
if(this.state.newItem === '') {
this.setState({errorMessage: 'true'});
} else {
return ( this.setState({ shoppingCart: this.state.shoppingCart.concat(this.state.newItem) }),
this.setState({newItem: ''}),
this.setState({errorMessage: 'false'})
)}
}
handleSubmit = (e) => {
e.preventDefault()
}
onRemoveItem = (i) => {
this.setState(state => {
const shoppingCart = state.shoppingCart.filter((item, j) => i !== j);
return {shoppingCart}
})}
render() {
return (
<div>
<form onSubmit ={this.handleSubmit}>
Shopping Cart Items
<br></br>
{ this.state.errorMessage === 'true' &&
<p className='error'> Please enter an item </p> }
<ul>
{this.state.shoppingCart.map((item, index,) => {
return <ShoppingCartList
item={item}
index={index}
onRemoveItem={this.onRemoveItem}
/>
})}
</ul>
<input
placeholder='Enter your item here'
value={this.state.newItem}
onChange={this.handleChange}
></input>
<button type='submit' onClick={this.handleClickAdd}>Add to Shopping list</button>
</form>
</div>
)
}
}
export default App;
Child
[code]
import React from 'react';
function ShoppingCartList ({item,index, onRemoveItem}) {
return (
<li key={item}>{item} <button type="button" onClick={() => onRemoveItem(index)}>Delete</button></li>
)
}
export default ShoppingCartList;
Issues
React keys should be defined on the element/component being mapped, inside the child component is the wrong location
Solution
When adding items to the shopping cart, generate the unique id when adding to state.
Use the item id as the react key in the parent when mapping the cart
items, and as a way to identify the item to be removed from the cart.
Update handleClickAdd to create a new item object with id and value. Spread the existing cart array into a new array and append the new item object to the end.
handleClickAdd = (e) => {
if (this.state.newItem === "") {
this.setState({ errorMessage: true });
} else {
this.setState((prevState) => ({
shoppingCart: [
...prevState.shoppingCart,
{
id: uuidv4(), // <-- new unique id
value: prevState.newItem // <-- item value
}
],
newItem: "",
errorMessage: false
}));
}
};
Update onRemoveItem to take an id to filter by.
onRemoveItem = (id) => {
this.setState((prevState) => ({
shoppingCart: prevState.shoppingCart.filter((item) => item.id !== id)
}));
};
Update your render to add the react key to ShoppingCartList.
{this.state.shoppingCart.map((item) => {
return (
<ShoppingCartList
item={item}
key={item.id}
onRemoveItem={this.onRemoveItem}
/>
);
})}
Update ShoppingCartList to render the item value and pass the item id to the remove item callback.
const ShoppingCartList = ({ item, onRemoveItem }) => (
<li>
{item.value}{" "}
<button type="button" onClick={() => onRemoveItem(item.id)}>
Delete
</button>
</li>
);
Related
I have one quick question, I want to move toggled item from the pending list to the completed item list when item.completed === true. So far, I created two different state arrays (list: [], and donelist: []) and passed the item based on the item.completed. There are a few errors I am facing: First: The item is not removed from the pending list but showed in the completed item list. Second: completed item list creating duplicate items when clicking on the toggle button in completed list.
Objective: when I click on the pending item's toggle button, it should move to the completed item list and vice versa.
import React from "react";
import {
Container,
DoneContainer,
TaskContainer,
Input,
Button,
FormContainer,
OrderList,
Div,
PendingItem,
CompltedItem,
Para
} from "./App.styles.js";
import "./styles.css";
const Item = ({ item, handleToggle, handleDelete, dublicate }) => (
<OrderList item={item}>
<Div item={item}>{item.value}</Div>
<button onClick={() => handleToggle(item)}>Toggle</button>
<button onClick={() => handleDelete(item)}>Delete</button>
</OrderList>
);
class App extends React.Component {
state = {
list: [],
donelist: [],
inputValue: "",
dublicate: false
};
handleClick = (e) => {
this.setState({ inputValue: e.target.value });
};
handleDelete = (item) => {
const newlist = this.state.list.filter((element) => element.id !== item.id);
this.setState({ list: newlist });
};
handleSubmit = (e) => {
e.preventDefault();
this.handleList(this.state.inputValue);
this.setState({ inputValue: "" });
};
handleToggle = (item) => {
const newlist = this.state.list.map((element) => {
if (element.id === item.id) {
element.completed = !element.completed;
if (element.completed) {
this.setState({ donelist: [...this.state.donelist, element] });
}
}
return element;
});
this.setState({ list: newlist });
};
getDublicate = (value) => {
return this.state.list.find((item) => item.value === value);
};
handleList = (value) => {
if (this.getDublicate(value)) {
this.setState({ dublicate: !this.state.dublicate });
} else {
const item = { value, id: `${Math.random()}`, completed: false };
const newList = [...this.state.list, item];
this.setState({ list: newList });
}
};
render() {
console.log(this.state.donelist);
return (
<Container>
<FormContainer onSubmit={this.handleSubmit}>
<Input onChange={this.handleClick} value={this.state.inputValue} />
<Button>Click</Button>
</FormContainer>
<PendingItem>Pending item list: </PendingItem>
<TaskContainer>
<ul>
{this.state.list.map((element) => (
<Item
item={element}
handleToggle={this.handleToggle}
handleDelete={this.handleDelete}
/>
))}
</ul>
</TaskContainer>
<CompltedItem>Completed item list: </CompltedItem>
<DoneContainer>
<ul>
{this.state.donelist.map((element) => (
<Item
item={element}
handleToggle={this.handleToggle}
handleDelete={this.handleDelete}
/>
))}
</ul>
</DoneContainer>
</Container>
);
}
}
export default App;
codesandbox link: https://codesandbox.io/s/react-todo-app-2syu10?file=/src/App.js:0-2841
I'm just refreshing myself on functional components and react state hooks, building a simple react todo list app- all the simple functionalities are built out but I have this one bug during initial state where there is an empty task rendering in the list. What am I missing? Any help would be greatly appreciated. :)
App.js:
import TodoList from './TodoList'
function App() {
return (
<div>
<TodoList />
</div>
);
}
export default App;
Todolist.js:
import React, {useState} from 'react'
import NewTodoForm from './NewTodoForm'
import Todo from './Todo'
const TodoList = () => {
const [state, setState] = useState({
list: [{title: "", id: ""}]
})
const addTodo = (newTodo) => {
setState({
list: [...state.list, newTodo]
})
console.log('after state change in addtodo', state.list.title)
}
const remove = (toDoId) => {
console.log('logging remove')
setState({
list: state.list.filter(todo => todo.id !== toDoId)
})
}
const strike = e => {
const element = e.target;
element.classList.toggle("strike");
}
const update = (id, updatedTask) => {
//i cant mutate state directly so i need to make a new array and set that new array in the state
const updatedTodos = state.list.map(todo => {
if (todo.id === id) { // find the relevant task first by mapping through existing in state and add updated info before storing it in updatedtodos
return { ...todo, title: updatedTask}
}
return todo
})
console.log('updated todos', updatedTodos)
setState({
list: updatedTodos
})
console.log('list after updating state')
}
return (
<div className="TodoList">
<h1>Todo List<span>A simple react app</span></h1>
<NewTodoForm addingTodo={addTodo}/>
{ state.list.map(todo => <Todo id={todo.id} key={todo.id} title={todo.title} updateTodo={update} strikeThrough={strike} removeTodo={() => remove(todo.id)} />) }
</div>
)
}
export default TodoList
Todo.js:
import React, {useState} from 'react'
const Todo = ({id, title, removeTodo, strikeThrough, updateTodo}) => {
const [state, setState] = useState({
isEditing: false,
})
const [task, setTask] = useState(title);
const handleUpdate = (e) => {
e.preventDefault()
updateTodo(id, task)
setState({ isEditing: false})
}
const updateChange = (e) => {
// setState({...state, [e.target.name]: e.target.value})
setTask(e.target.value)
console.log(task)
}
return (
<div>
{state.isEditing ?
<div className="Todo">
<form className="Todo-edit-form" onSubmit={handleUpdate}>
<input
type="text"
value={task}
name="task"
onChange={updateChange}
>
</input>
<button>Submit edit</button>
</form>
</div> :
<div className="Todo">
<ul>
<li className="Todo-task" onClick={strikeThrough}>{title}</li>
</ul>
<div className="Todo-buttons">
<button onClick={() => setState({isEditing: !state.isEditing})}><i class='fas fa-pen' /></button>
<button onClick={removeTodo}><i class='fas fa-trash' /></button>
</div>
</div>
}
</div>
)
}
export default Todo
You're rendering your to-do's with:
{ state.list.map(todo => <Todo id={todo.id} key={todo.id} title={todo.title} updateTodo={update} strikeThrough={strike} removeTodo={() => remove(todo.id)} />) }
Your initial state is:
{ list: [{title: "", id: ""}] }
The above state will cause React to render an empty to-do item for you. Once you clear the array, you should not see anything. Another option is to change your rendering and add a conditional that checks if to-do item values are empty, to not render them.
The initial state in TodoList seems to be having list:[{title: "", id: ""}], which contains empty title and id. Since it's mapped to create Todo, I think it starts with an empty Todo.
I render a React list and there is an Edit button in each list item. I wanted to toggle to switch from the data to the input form and back. Similar to this application in this article: https://medium.com/the-andela-way/handling-user-input-in-react-crud-1396e51a70bf. You can check out the demo at: https://codesandbox.io/s/fragrant-tree-0t13x.
This is where my React Component display the list:
import React, { Component } from "react";
import PriceBox from "../SinglePricebox/index";
// import SecurityForm from "../SecurityForm/index";
import AddPriceForm from "../AddPriceForm/index";
// import { uuid } from "uuidv4";
export default class PriceForm extends Component {
constructor(props) {
super(props);
this.state = {
priceArr: this.props.pricelist,
// newPriceArr: this.props.updatePrice,
showPricePopup: false,
addPricePopup: false,
isToggleOn: true,
date: props.date || "",
number: props.number || ""
};
}
updateInput = ({ target: { name, value } }) =>
this.setState({ [name]: value });
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
togglePricePopup = () => {
this.setState(prevState => ({
showPricePopup: !prevState.showPricePopup
}));
};
addPricePopup = () => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup
}));
};
/* adds a new price to the list */
addPrice = newPrice => {
this.setState(prevState => ({
addPricePopup: !prevState.addPricePopup,
// spreads out the previous list and adds the new price with a unique id
priceArr: [...prevState.priceArr, { ...newPrice }]
}));
};
// handlePriceSubmission = () => {
// const { updatePrice } = this.props;
// this.addPricePopup();
// updatePrice(priceArr);
// };
toggleItemEditing = (index) => {
this.setState(prevState => ({
priceArr: prevState.priceArr.map(priceItem => {
// isToggleOn: !state.isToggleOn;
})
}));
};
// toggleItemEditing = index => {
// this.setState({
// items: this.state.items.map((item, itemIndex) => {
// if (itemIndex === index) {
// return {
// ...item,
// isEditing: !item.isEditing
// }
// }
// return item;
// })
// });
// };
render() {
// const { updatePrice } = this.props;
return (
<div className="popup">
<div className="popup-inner">
<div className="price-form">
<h2>Prices</h2>
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
<div className="buttons-box flex-content-between">
<button
type="button"
onClick={this.addPricePopup}
className="btn add-button">Add +</button>
{this.state.addPricePopup && (
<AddPriceForm
addPrice={this.addPrice}
cancelPopup={this.addPricePopup}
/>
)}
<div className="add-btns">
<button
type="button"
onClick={() => this.props.closeUpdatePopup()}
className="btn cancel-button"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
The list inside the component above is:
<div className="scroll-box">
{this.state.priceArr.map((props) => (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
))}
</div>
And this is the single list item:
import React, { Component } from "react";
export default class SinglePricebox extends Component {
state = {
showPopup: false, //don't show popup
todaydate: this.props.date
};
/* toggle and close popup edit form window */
togglePopup = () => {
this.setState(prevState => ({
showPopup: !prevState.showPopup
}));
};
toggleEditPriceSubmission = getPriceIndex => {
const { toggleItemEditing, date } = this.props;
// toggle the pop up (close)
// this.showPopup();
toggleItemEditing({ ...getPriceIndex, date });
console.log("date?", date);
};
/* handles edit current security form submissions */
// handleEditSecuritySubmission = editSecurity => {
// const { editCurrentSecurity, id } = this.props;
// // toggle the pop up (close)
// this.togglePopup();
// // sends the editSecurity fields (name, isin, country) + id back to
// // App's "this.editCurrentSecurity"
// editCurrentSecurity({ ...editSecurity, id });
// };
render() {
return (
<div className="pricebox">
<article className="pricetable">
{this.toggleEditPriceSubmission
? "editing" : "not editing"}
<table>
<tbody>
<tr>
<td className="date-width">{this.props.date}</td>
<td className="price-width">{this.props.number}</td>
<td className="editing-btn">
<button
type="button"
className="edit-btn"
onClick={this.toggleEditPriceSubmission}
>
{this.toggleEditPriceSubmission ? "Save" : "Edit"}
</button>
</td>
<td>
<button
type="button"
className="delete-btn">
X
</button>
</td>
</tr>
</tbody>
</table>
</article>
</div>
);
}
}
I have been struggling all afternoon to toggle the edit button in each list item. I was attempting to get the key of each list item which is the this.prop.date.
You can see my code in detail at CodeSandBox: https://codesandbox.io/s/github/kikidesignnet/caissa
I would create a component which will handle the list item as a form and update it as if it was SecurityForm.
{this.state.priceArr.map((props) => {
if(props) {
return <PriceListForm methodToUpdate {...props} />
}else {
retun (
<PriceBox
{...props}
key={props.date}
// toggleItemEditing={this.toggleItemEditing()}
onChange={this.handleItemUpdate}
/>
);
}
})}
and make PriceListForm look like PriceBox but use inputs to capture new data. This way you will have two different components with less complicated logic instead of having a huge component with complex validations to check if you will display an input or not.
create a funtion named Edit to update the State
Edit = (id) => {
this.setState({edit: !this.state.edit, id})
}
and instead of that
<td className="country-width">{this.props.country}</td>
do something like
<td className="country-width">{this.state.edit ? <input type="text" value={this.props.country} onChange={() => UPDATE_THE_CONTENT}/> : this.props.isin}</td>
and call the Edit function onclick of Edit Button and pass ID of that td as a param to update it.
NOTE: by default THE VALUE OF EDIT STATE is false/null.
you can use onChange to update that box or some other techniques like create a button along with it and use that to update it.
hope this might help you
I have run into a problem with my React application. So far there is enough functionality in place to add To-Do items to the list, remove them by index and mark them done (text-decoration: line-through).
When I remove an item that is already crossed out, I would expect the other items to keep their own state, however they don't. Here's what I mean.
Let's remove the crossed out item
Why is the bottom one crossed out now?
Here's my code
ToDoApp.js
import React from 'react';
import Header from './Header';
import AddToDo from './AddToDo';
import FilterToDo from './FilterToDo';
import ToDoList from './ToDoList';
import ListButtons from './ListButtons';
export default class ToDoApp extends React.Component {
state = {
toDos: []
};
handleAddToDo = (toDo) => {
if (!toDo) {
return "Nothing was added!";
}
this.setState((prevState) => ({
toDos: prevState.toDos.concat([toDo])
}));
};
handleRemoveToDo = (removeIndex) => {
this.setState((prevState) => ({
toDos: prevState.toDos.filter((toDo, index) => index !== removeIndex)
}));
};
render() {
return (
<div>
<Header />
<AddToDo
handleAddToDo={this.handleAddToDo}
/>
<FilterToDo />
<ToDoList
toDos={this.state.toDos}
handleRemoveToDo={this.handleRemoveToDo}
/>
<ListButtons />
</div>
);
};
};
ToDoList.js
import React from 'react';
import ToDoListItem from './ToDoListItem';
const ToDoList = (props) => (
<div>
<h3>To Do List</h3>
<div>
{props.toDos.map((toDo , index) => (
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
</div>
</div>
);
export default ToDoList;
ToDoListItem.js
import React from 'react';
export default class ToDoListItem extends React.Component {
state = {
done: false
};
handleDoneTrigger = () => {
this.setState((prevState) => ({ done: !prevState.done }));
};
render() {
return (
<div>
<p
className={this.state.done ? "done" : ""}
>{this.props.toDoTitle}</p>
<button onClick={(e) => {
this.props.handleRemoveToDo(this.props.index)
}}>Remove</button>
<button onClick={this.handleDoneTrigger}>Done</button>
</div>
);
}
};
The problem is with this piece of code:
<ToDoListItem
key={index}
index={index}
toDoTitle={toDo}
handleRemoveToDo={props.handleRemoveToDo}
/>))}
as you set the index as key of ToDoListItem. Instead of index assign some unique key to each element because when you delete an item its index assigned to following item in the list.
This will be helpful to dig more into deep: https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
I'm trying to update some immutable data. However, I am doing it incorrectly and it doesn't work.
My understanding, is I need to pass it a unique it (eg the key) so it knows which item in state to update. But this may be wrong, and I would like to do this the proper way. I tried the immutability helper, but had no success.
I will also want to update/add/remove nested items in an immutable array.
I put this in a codeSandbox for ease https://codesandbox.io/s/mz5WnOXn
class App extends React.Component {
...
editTitle = (e,changeKey) => {
this.setState({
movies: update(this.state.movies, {changeKey: {name: {$set: e.target.value}}})
})
console.log('Edit title ran on', key)
};
...
render() {
...
return (
<div>
{movies.map(e => {
return (
<div>
<input key={e.pk} onChange={this.editTitle} value={e.name} changeKey={e.pk} type="text" />
With genres:
{e.genres.map(x => {
return (
<span key={x.pk}>
<input onChange={this.editGenres} value={x.name} changeKey={x.pk} type="text" />
</span>
);
})}
</div>
);
})}
</div>
);
}
}
Couple of things,
1. By looking at your data i don't see any pk property maybe its a typo (are you trying to use the id property instead?).
2. The key prop should be on the root element that you return on each iteration of your map function.
3. You can't just add none native attributes to a native Html element instead you can use the data-* like data-changeKey and access it via e.target.getAttribute('data-changeKey')
Something like this:
import { render } from 'react-dom';
import React from 'react';
import { reject, pluck, prop, pipe, filter, flatten, uniqBy } from 'ramda';
import { smallData } from './components/data.js';
import './index.css';
import Result from './components/Result';
import update from 'react-addons-update';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: smallData.movies,
selectedFilters: {},
searchQuery: '',
};
}
editTitle = (e) => {
const attr = e.target.getAttribute('data-changeKey');
this.setState({
movies: update(this.state.movies, {
// what are you trying to do here?
//changeKey: { name: { $set: e.target.value } },
}),
});
console.log('edit title ran on', attr);
};
onSearch = e => {
this.setState({
searchQuery: e.target.value,
});
};
render() {
const { movies } = this.state;
return (
<div>
{movies.map((e, i) => {
return (
<div key={i}>
<input
onChange={this.editTitle}
value={e.name}
data-changeKey={e.id}
type="text"
/>
With genres:
{e.genres.map((x,y) => {
return (
<span key={y}>
<input
onChange={this.editTitle}
value={x.name}
data-changeKey={x.id}
type="text"
/>
</span>
);
})}
Add new genre:
<input type="text" />
</div>
);
})}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Index is what update needs in order to know which data to change.
Firstly, map sure your map is creating an index:
{movies.map((e,index) => {
return (
<input key={e.pk} onChange={e => this.editTitle(e, index)} value={e.name} >
...
Then pass the index to the immutability-helper update like so:
to:
editTitle = (e, index) => {
this.setState({
movies: update(this.state.movies, {[index]: {name: {$set: e.target.value}}})
})