React class component. Todo app. How to store data with localStorage - javascript

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,
...
}

Related

React setState not updating the state

I'm new to React and I'm having some trouble with changing sate with setState method. I'm trying to update state of ToDo item when I click on the checkbox. When I'm debugging handleChange method it changes the state of the item for a while. Unfortunatelly, the final state is like at the beginning (state of the checkbox is never changed). Does somebody know where the problem is?
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData,
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id){
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <ToDoItem key={item.id} todoItem={item}
handleChange={this.handleChange}/>)
return (
<div className="App">
{todoItems}
</div>
);
}
}
export default App;
Here is my ToDoItem component:
import React from 'react'
function ToDoItem(props){
return (
<div className = "todo-list">
<div className = "todo-item">
<input type="checkbox"
checked={props.todoItem.completed}
onChange = {() => props.handleChange(props.todoItem.id)}/>
<p>{props.todoItem.text}</p>
</div>
</div>
)
}
export default ToDoItem
Just use this handler and let me know if works.
handleChange(id){
const list = this.state.todos.slice();
const index = list.findIndex(o => o.id === id);
list[index].completed = !list[index].completed;
this.setState({ todos: list });
}
I think the problem is here:
handleChange(id){
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return { ...todo }; // need to return a new object
})
return {
todos: updatedTodos
}
})
}
todos are objects and event if todos are being set as a new object every time, its items referencing to the same object, and it is used by ToDoItem.

My handleChange method of simple react todoapp is not doing what it needs to do

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:

handleRemove function for Todo List app with an array of objects

So I am making this app super simple, however I can't get my handleRemove to work properly. filteredTodos comes out to be a list of all the same todos. This is my code.
I have tried even looking at other solutions online but for some reason this filter function in handleRemove does not filter anything out of the state.
import React, { Component } from 'react';
class Main extends Component {
constructor(props){
super(props);
this.state = {
todos: [],
inputValue: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleChange = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
const newTodo = this.state.inputValue;
if (this.state.inputValue === ''){
alert('Please Enter a Todo!');
} else {
this.setState((prevState) => ({
todos: [...prevState.todos,
{
message: newTodo,
id: this.state.todos.length
}
]
}));
this.setState({inputValue:''});
}
}
handleRemove (id) {
const filteredTodos = this.state.todos.filter(todo => todo.id !== id);
this.setState({
todos: filteredTodos
});
console.log(filteredTodos);
}
render(){
const mappedTodos = this.state.todos.map((item, i) =>
<div key={i} id={this.state.todos[i].id}>
{item.message} <button type='submit' onClick={this.handleRemove}>X</button>
</div>
)
return(
<div className='main-page'>
<div className='input'>
<input type='text' placeholder='Enter Your Todo' value={this.state.inputValue} onChange={this.handleChange} />
<button type='submit' onClick={this.handleSubmit}>Add</button>
</div>
<div className='todos'>
{mappedTodos}
</div>
</div>
)
}
}
export default Main;
Your handleRemove function requires an id you can see it by the value in the round brackets
handleRemove (id)
to fix the problem you just have to pass the parameter just like this:
const mappedTodos = this.state.todos.map((item, i) =>
<div key={i} id={this.state.todos[i].id}>
{item.message} <button type='submit' onClick={this.handleRemove(this.state.todos[i].id)}>X</button>
</div>
)

Removing from react array

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

React Todo Delete Button Removes all listed items at once

I have 2 files
App.js
import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [],
newTodoDescription: ''
};
this.deleteTodo = this.deleteTodo.bind(this);
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { id: this.state.todos.id, description: this.state.newTodoDescription, isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
deleteTodo(id) {
const remainingToDos = this.state.todos.filter((todo, remainingToDos) => {
if(todo.id !== remainingToDos.id) return todo; });
this.setState({ todos: remainingToDos });
}
render() {
return (
<div className="App">
<h1>Add a ToDo!</h1>
<form onSubmit={ (e) => this.handleSubmit(e)}>
<input type="text"
value={ this.state.newTodoDescription }
onChange={ (e) => this.handleChange(e) }
/>
<input type="submit" value="Add Todo" />
</form>
<ul>
{ this.state.todos.map( (todo) =>
<ToDo key={ todo.id }
description={ todo.description }
isCompleted={ todo.isCompleted }
toggleComplete={ () => this.toggleComplete(todo) }
onDelete={ this.deleteTodo }
/>
)}
</ul>
</div>
);
}
}
export default App;
ToDo.js
import React, { Component } from 'react';
class ToDo extends Component {
render() {
return (
<li>
<input type="checkbox" checked={ this.props.isCompleted } onChange={ this.props.toggleComplete } />
<span>{ this.props.description } {''}</span>
<button onClick={() => this.props.onDelete(this.props.id)}>Remove Todo</button>
</li>
);
}
}
export default ToDo;
What I am trying to accomplish: Add many todos to the list. Click on the remove todo button. ONLY the todo that is selected will be removed.
I am VERY new to react and cannot figure this out. In my deleteToDo method I am trying to filter out the todos and only keep the todos that are current. I am unclear if I am using .filter properly or not.
Problem is that filter() method should return a condition, not value:
deleteTodo(id) {
const remainingToDos = this.state.todos.filter((todo, remainingToDos) => {
return (todo.id !== remainingToDos.id)
});
this.setState({ todos: remainingToDos });
}
I am fairly sure you have simply over-complicated your code. You are not using the parameter id that you have passed into your method at all. Your deleteTodo method could simply be:
deleteTodo = (id) => {
var remainingToDos = this.state.todos.filter((todo) => {
return todo.id === id
});
this.setState({ todos: remainingToDos })
}

Categories