The problem is any item button click will delete the 1st index item in the array.
I looked at these resources on handling deleting an item in an array in react.
How to remove item in todo list using React
Removing element from array in component state
React Binding Patterns
I've tried changing how my handler is called in TodoList and TodoItemLIst and that causes the handler not to fire on click. I've tried different methods of binding the handler - adding a param has no effect on it -bind(this) breaks it & isn't necessary because I'm using a function.
I've tried setting state different ways using a filter method. No change happens...
this.setState((prevState) => ({
todoItems: prevState.todoItems.filter(i => i !== index)
}));
I'm not understanding where/what the problem is.
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
listItemValue: props.value || '',
todoItems: [
{id: _.uniqueId(), item: 'Learn React.'},
{id: _.uniqueId(), item: 'Improve JS skills.'},
{id: _.uniqueId(), item: 'Play with kittens.'}
]
};
}
handleChange = (event) => {
let value = event.target.value;
this.setState({
value: this.state.value,
listItemValue: value
});
}
handleSubmit = (event) =>{
event.preventDefault();
this.setState({
value: '',
listItemValue: ''
});
}
addTodoItem = () => {
let todoItems = this.state.todoItems.slice(0);
todoItems.push({
id: _.uniqueId(),
item: this.state.listItemValue
});
this.setState(prevState => ({
todoItems: [
...prevState.todoItems,
{
id: _.uniqueId(),
item: this.state.listItemValue
}]
}))
};
deleteTodoItem = (index) => {
let todoItems = this.state.todoItems.slice();
todoItems.splice(index, 1);
this.setState({
todoItems
});
}
render() {
return (
<div className="App">
<h1>Todo List</h1>
<TodoListForm name="todo"
onClick={ ()=>this.addTodoItem() }
onSubmit={ this.handleSubmit }
handleChange={ this.handleChange }
value={ this.state.listItemValue } />
<TodoList onClick={ ()=>this.deleteTodoItem() }
todoItems={ this.state.todoItems }/>
</div>
);
}
}
const TodoList = (props) => {
const todoItem = props.todoItems.map((todo) => {
return (
<TodoListItem onClick={ props.onClick }
key={ todo.id }
id={ todo.id }
item={ todo.item }/>
);
});
return (
<ul className="TodoList">
{todoItem}
</ul>
);
}
const TodoListItem = (todo, props) => {
return (
<li className="TodoListItem">
<div className="TodoListItem__Item">{todo.item}
<span className="TodoListItem__Icon"></span>
<button onClick={ todo.onClick }
type="button"
className="TodoListItem__Btn">×</button>
</div>
</li>
)
};
In the deleteTodoItem method, try just
let todoItems = this.state.todoItems.slice(0, -1);
and remove the call to splice().
Related
I'd like to store todo data with localStorage so that it won't disappear after refreshing the page.
I used React class component when started creating.
I've added 'handleFormSubmit' and 'ComponentDidMount' methods.
nothing stores in localStorage when I type todo and choose date.
get an error in ComponentDidMount with
Line 'const result = localData ? JSON.parse(localData) : [];'
:SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
how can I set and get items?
It would be really appreciated if I could get help.
I'd like to make this app really work.
import React from "react"
import TodoItem from "./components/TodoItem"
import todosData from "./components/todosData"
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: todosData,
//setTodos: todosData,
newItem: "",
deadline: "",
editing: false
}
this.handleChange = this.handleChange.bind(this)
this.addTodo = this.addTodo.bind(this)
this.updateInput = this.updateInput.bind(this)
this.deleteItem = this.deleteItem.bind(this)
this.updateItem = this.updateItem.bind(this)
this.updateDeadline = this.updateDeadline.bind(this)
this.updateInputDeadline = this.updateInputDeadline.bind(this)
this.editItem = this.editItem.bind(this)
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if (todo.id === id) {
return { ...todo, completed: !todo.completed };
} else {
return todo;
}
});
return { todos: updatedTodos };
});
}
addTodo(e) {
e.preventDefault();
const newTodo = {
id: this.state.todos.length + 1,
text: this.state.newItem,
completed: false,
deadline: this.state.deadline
}
const newTodos = this.state.todos.concat([newTodo]);
this.setState({
todos: newTodos
})
}
updateInput(value, id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if(todo.id === id) {
return {...todo, text: value}
}else {
return todo;
}
})
return {todos: updatedTodos}
})
}
updateInputDeadline(value, id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
if(todo.id === id) {
console.log(value, id);
return {...todo, deadline: value}
}else {
return todo;
}
})
return {todos: updatedTodos}
})
}
updateItem(e) {
this.setState({
newItem: e.target.value
})
}
updateDeadline(e) {
this.setState({
deadline: e.target.value
})
}
deleteItem(id){
const filteredItems= this.state.todos.filter(item =>
item.id!==id);
this.setState({
todos: filteredItems
})
}
editItem(id) {
this.setState({
editing: id
})
}
handleFormSubmit() {
const { todo, deadline } = this.state;
localStorage.setItem('todo', JSON.stringify(todo));
localStorage.setItem('deadline', deadline);
};
componentDidMount() {
const localData = localStorage.getItem('todo');
const result = localData ? JSON.parse(localData) : [];
const deadlineData = localStorage.getItem('deadline');
this.setState({ result, deadlineData });
}
render() {
const todoItems = this.state.todos.map
(item =>
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
addTodo={this.addTodo}
deleteItem={this.deleteItem}
updateInput={this.updateInput}
updateInputDeadline={this.updateInputDeadline}
isEdited={this.state.editing === item.id}
editItem={this.editItem}
/>)
return (
<div className="todo-list">
<Timer />
<form onSubmit={this.handleFormSubmit}>
<div className="add-todo">
<label>Add an item...</label>
<input
type="text"
name="todo"
placeholder="Type item here..."
value={this.state.newItem}
onChange={this.updateItem}
/>
</div>
<div className="date">
<label htmlFor="deadline">Deadline</label>
<input
type="date" id="start" name="deadline"
min="2021-01-01"
max="2024-12-31"
value={this.state.deadline}
onChange={this.updateDeadline}
/>
</div>
<button type="submit" onClick={this.addTodo}>Add to the list</button>
</form>
{todoItems.length === 0 ? <p>No items</p> : null}
<div className="todoitems">
{todoItems}
</div>
</div>
)
}
}
export default App
When you press the button, there are two events that you are trying to call - addTodo and handleFormSubmit. Since you are calling e.preventDefault() in addTodo, the submit event is never called. You could do all of the actions you need in one of the methods.
My guess is that you are either trying to JSON.parse an array instead of an object, or the value of todo is undefined. You are trying to get todo out of this.state, but you only have todos in your state, so it might be a typo. The same goes for deadline.
You are doing the setting and getting correctly. You could actually get data from localStorage even when you are first setting the state in constructor. But the componendDidMount approach you tried is also good.
constructor(props) {
super(props)
const cachedTodos = localStorage.getItem("todo")
this.state = {
todos: cachedTodos ?? todosData,
...
}
In the output, Only the default completed values are checked! not able change the checks of tasks.
These are my Java script files
app.js
class App extends Component {
constructor() {
super()
this.state = {
todos: Todosdata
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const udpated = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: udpated
}
})
}
render() {
const todoelements = this.state.todos.map(item => <ToDoItem key={item.id}
todoitem={item}
handleChange={this.handleChange} />)
return (
<div className="App">
< div className="todo-list" >
{todoelements}
</div >
</div>
)
}
}
ToDoItem.js
const ToDoItem = (props) => {
const Afterstyle = {
fontColor: "red",
textDecoration: "line-through"
}
return (
<div className="todo-item">
<input type="checkbox"
checked ={props.todoitem.completed}
onChange ={() => props.handleChange(props.todoitem.id)} />
<p style={props.todoitem.completed ? Afterstyle : null}>{props.todoitem.task}</p>
</div>
)
}
i did console log inside if condition of handle Change method,its printing 2 times.
I am stuck at this for hours please fix this!
You are mutating the todo item instead of creating a new one. Change your handler like that:
handleChange(id) {
this.setState(prevState => {
const udpated = prevState.todos.map(todo => {
if (todo.id === id) {
// if the id matches return a new object
return {...todo, completed: !todo.completed};
}
return todo
});
return {todos: udpated};
}
}
Live Demo:
Implemented writing data from the form to the array. Everything works. but now I want to implement data output when I click on " SEND ARR "
When I hang up a click on a button, respectively, the data is not displayed since in the function we access event.target.value
Please tell me how to rewrite the line so that I can display data when I click on the button? thank
home.js
import React from "react";
import "./../App.css"
export default class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState({
count: this.state.count + 1
});
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue(event) {
let newArr = this.state.arr;
let newlist = event.target.value;
if (event.target.value) {
newArr.push(newlist)
}
this.setState({
inputValue: newArr
});
event.target.value = '';
};
render() {
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" onClick={this.updateInputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button>SEND ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
Store the data from the input into invputValue each time the input is updated and on the click of the button update the arr content with the old values (...arr) plus the current input value (this.state.inputValue) .
To make sure the old values are not deleted the arr is defined at the top of the class let arr = []. If you don't want it there you can instantiate it in the constructer which will run only once. i.e. this.arr = []
let arr = []
class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState({
count: this.state.count + 1
});
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue = e => {
this.setState({ inputValue: e.target.value })
}
displayData = () => {
arr = [...arr ,this.state.inputValue]
this.setState({ arr, inputValue: "" })
}
clearData = () => {
this.setState({ arr: [] })
}
render() {
console.log("this.state.arr:", this.state.arr)
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" onChange={this.updateInputValue} value={this.state.inputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button onClick={this.displayData}>SEND ARR</button>
<button onClick={this.clearData}>CLEAR ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
Instead of using onClick on input, use onChange and update value in state i.e. make the input a controlled component . Post that onClick of button take the value from state and push to the array and clear the input value
export default class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState(prevState => ({
count: prevState.count + 1,
inputValue: [...prevState.inputValue, prevState.name],
name: ""
}));
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue(event) {
let newVal = event.target.value;
this.setState({
name: newVal
});
};
render() {
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" value={this.state.name} onChange={this.updateInputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button>SEND ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
I am new in react and javascript and I'm trying to create a todo app to learn, everything is working so far except 2 things, the delete and if is completed func. The goal that I want to achieve is to have a button onClick to change the status of completed from false to true and then I will pass a line-through to the particular todo. After this I will have 3 different counters to show the total, completed and incompleted num of todos. The code is as follow:
class App extends Component {
constructor() {
super();
this.state = {
text: '',
notes: [
{todo: "test", completed: true},
{todo: "test2", completed: false}
]
}
}
handleChange = (e) => {
this.setState({ text: e.target.value })
}
handleSubmit = (e) => {
e.preventDefault();
if(!this.state.text.length) { return }
const newTodo = {
todo: this.state.text,
completed: false
}
const notes = this.state.notes
notes.push(newTodo)
this.setState({
text: '',
notes: notes
})
}
handleClick = (index) => {
const notes = this.state.notes
notes[index].completed = !notes[index].completed
this.setState({ notes })
}
handleDelete = (index) => {
const notes = this.state.notes
notes.splice(index, 1)
this.setState({ notes })
}
render() {
let notes = this.state.notes.map((todo, index) => {
return <Todo key={index} note={todo}
deleteTodo={this.handleDelete} handleClick={this.handleClick}/>
});
return (
<div className="App">
<div className="notes-wrapper">
<Header />
<Form
handleSubmit={this.handleSubmit}
handleChange={this.handleChange}
text={this.state.text}
/>
{notes}
<Footer notesLength={this.state.notes.length} />
</div>
</div>
);
}
}
if I change the array of obj to a simple array like array=[1,2,3] I can do array.splice(index,1) and is working perfectly but in this case something is wrong with the index as I understand. The delete func is working but is deleting a wrong element not the clicked one and also if I do notes[1].completed = !notes[1].completed is working but on both buttons and it will change only the button in the first item. I dont know if is to complicated for a beginner. Thank you!
const Todo = (props) => {
return (
<div className="todo-wrapper">
<li style={{textDecoration: props.note.completed ?
'line-through' : 'none'}}>
<button className="btn btn-remove"
onClick={props.deleteTodo}>Remove</button>
{props.note.todo}
<button className="btn btn-status"
onClick={props.handleClick}>{props.note.completed ?
"Undo" : "Done"}</button>
</li>
</div>
);
There are two things that you need to take care of,
First: You are not passing the index to the functions handleDelete and handleClick from the Todo component.
Second: You should not mutate the original state. Treat it as if its immutable
handleClick = (index) => {
const notes = [...this.state.notes] // create a copy
notes[index].completed = !notes[index].completed
this.setState({ notes })
}
handleDelete = (index) => {
this.setState(prevState => ({ notes: [...prevState.notes.slice(0, index), ...prevState.notes.slice(index) }))
}
render() {
let notes = this.state.notes.map((todo, index) => {
return <Todo key={index} note={todo} index={index}
deleteTodo={this.handleDelete} handleClick={this.handleClick}/>
});
return (
<div className="App">
<div className="notes-wrapper">
<Header />
<Form
handleSubmit={this.handleSubmit}
handleChange={this.handleChange}
text={this.state.text}
/>
{notes}
<Footer notesLength={this.state.notes.length} />
</div>
</div>
);
}
And your Todo Component will have
<button className="btn btn-remove" onClick={() => props.deleteTodo(props.index)}>Remove</button>
and similarly
<button className="btn btn-status" onClick={() => props.handleClick(props.index)}>{props.note.completed ? "Undo" : "Done" }</button>
How do I remove an item from an array in react? I've tried a couple of things and it didnt work out. Just trying to make a basic todo app. I've updated my post to provide the render method to show where the deleteTodo is going. I've also updated my deleteTodo with an answer I got from this post. It kind of works, the only problem is it deletes all of the items in the todo list rather than just the single one.
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
// console.log('clicked')
const newTodo = {
value: this.state.inputValue,
done: false
}
const todos = this.state.todos;
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
// Take copy of current todos
const todos = [this.state.todos];
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
You just need to filter that value from array and set new filtered array in the setState.
deleteTodo = (value) => {
// Take copy of current todos
const todos = [...this.state.todos];
const filteredTodos = todos.filter( (item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
Your use of filter appears to be the problem.. To create a new array of elements without the value using filter, you can try something like this:
EDIT: Updated answer with a full working demo
import React, {Component} from 'react';
import './App.css';
class Form extends Component {
constructor(props) {
super(props);
this.textRef = React.createRef();
}
render() {
return (
<form onSubmit={(e)=>{e.preventDefault(); this.props.handleSubmit(this.textRef.current.value)}}>
<input type="text" ref={this.textRef}/>
<input type="submit" value="add"/>
</form>
);
}
}
class List extends Component {
render() {
return (
<ul>
{
this.props.todos.map((todo) => (
<li key={todo.value}>
<p><input type="checkbox" checked={todo.done}/>{todo.value} </p>
<input type="button" onClick={() => (this.props.deleteTodo(todo.value))} value="delete"/>
</li>
))
}
</ul>
);
}
}
class App extends Component {
state = {
inputValue: "",
todos: [{
value: "walk the dog",
done: false
},
{
value: "do the dishes",
done: false
}
]
}
addTodo = (e) => {
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (value) => {
const newTodo = {
value,
done: false
}
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({
todos,
inputValue: ''
})
}
deleteTodo = (value) => {
const todos = this.state.todos;
const filteredTodos = todos.filter((item) => item.value !== value);
this.setState({
todos: filteredTodos
})
}
render() {
return (
<div className="App">
<Form
addTodo={this.addTodo}
handleSubmit={this.handleSubmit}
/>
<List
todos={this.state.todos}
deleteTodo={this.deleteTodo}
/>
</div>
);
}
}
export default App;
Also, I changed your handleSubmit method to create a new Array to keep in line with React's functional paradigm
i have used lodash for such this.
lodash is a library for doing such thing https://lodash.com/
if you can get the same value object that you inserted while adding the rest is quite easy
you lodash to find the index in which you have the object in the array
on your delete function
const todos = this.state.todos;
const itemToRemove = {value: "walk the dog",done: false};
var index = _.findIndex(todos, itemToRemove);
const filteredTodos = todos.splice(index, 1)
this.setState({
todos: filteredTodos
})
Hope this will help