Reputation: 345
I'm implementing Todo with checkboxes. I have a id,content,status properties in the state. Status has 3 states(pending,done, deleted). There is a checkbox for the todolist. If the status is pending then checkbox shouldn't be checked. If the status is done then checkbox should be checked.By default, status is pending.Checkbox should be checked/unchecked according to the status. On deleting the todo then status should be updated to deleted. Now I'm struck with checkbox implementation. It's not working as per the requirement.
App.js :
import React from 'react';
import Todo from './Todo';
import AddTodo from './AddTodo';
class App extends React.Component{
state={
todos:[
{id:1,content:'Buy milk1',status:'pending'},
{id:2, content:'buy carrots1', status: 'pending'},
{id:3,content:'Buy milk2',status:'done'},
{id:4, content:'buy carrots2', status: 'deleted'}
]
}
onDelete=(id)=>{
const todo = this.state.todos.find((todoItem => todoItem.id === id))
todo.status ='deleted';
this.setState([...this.state.todos]);
}
onChangeCheckbox=(id, checked)=>{
const todo = this.state.todos.find((todoItem => todoItem.id === id))
if(checked){
todo.status = 'done'
}
else{
todo.status = 'pending'
}
this.setState([...this.state.todos]);
}
addTodo=(todo)=>{
todo.id=Math.random();
todo.status = "pending";
let todos=[...this.state.todos,todo];
this.setState({todos});
}
render(){
return(
<div>
<h1>Todo's App</h1>
<AddTodo addTodo={this.addTodo} />
<Todo todos={this.state.todos} deleteTodo={this.onDelete} onChangeCheckbox=
{this.onChangeCheckbox} />
</div>
)
}
}
export default App;
AddTodo.js :
import React from 'react';
class AddTodo extends React.Component{
state={
content:''
}
handleChange=(e)=>{
this.setState({
content:e.target.value
});
}
handleSubmit=(e)=>{
e.preventDefault();
this.props.addTodo(this.state);
this.setState({
content:''
})
}
render(){
return(
<div>
<form onSubmit={this.handleSubmit}>
<input type='text' onChange={this.handleChange} value={this.state.content}
placeholder="Enter todo" />
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default AddTodo;
Todo.js:
import React, {useState} from 'react';
const Todo=({todos,deleteTodo, onChangeCheckbox})=>{
const [checked, setChecked] = useState(false);
const handleInputChange =(event,id,status)=>{
setChecked(status=='pending' ? !checked: checked)
onChangeCheckbox(id,!checked);
}
const todoList=todos.length ? (
todos.map(todo=>{
return(
<div key={todo.id}>
{(todo.status === 'pending' || todo.status === 'done' )&& (
<div>
{/* <input
type="checkbox"
checked={checked}
onChange={(event)=>handleInputChange(event,todo.id)}
/> */}
<input
type="checkbox"
checked={todo.status === 'pending' ? checked : !checked}
onChange=
{(event)=>handleInputChange(event,todo.id,todo.status)}
/>
<p style={todo.status =='pending'? {color:'red'}:
{color:'green',textDecoration:'line-through'}} >
{todo.content}
</p>
<button onClick={()=>deleteTodo(todo.id)}>Delete</button>
</div>
)}
</div>
)
})
):(
<p>You have no todos</p>
);
return(
<div>
{todoList}
</div>
)
}
export default Todo;
Thanks in advance.. Please find the codesandbox link here: https://codesandbox.io/s/awesome-ganguly-0r054?file=/src/Todo.js
Upvotes: 1
Views: 618
Reputation: 3798
that's happen because you let the checked status listen to the current component state
and not to the item status. and they are not in sync. so you have two solutions. first pass status value as props to the checked state to let the state changed when the status value updated and i think it is more expensive. I have other suggestion without the state at all.
// const [checked, setChecked] = useState(false); remove that, you no longer need it
const handleInputChange =(event,id,status)=>{
let isChecked = status=='pending' ? true: false;
onChangeCheckbox(id, isChecked);
}
also update the input check status
<input
type="checkbox"
checked={todo.status === 'pending' ? false : true}
onChange= {(event)=>handleInputChange(event,todo.id,todo.status)}
/>
https://codesandbox.io/s/competent-hugle-jn22o?file=/src/Todo.js
Upvotes: 1
Reputation: 202846
You've a few issues with your code. The biggest concern is the state mutations in your onDelete
, onChangeCheckbox
, and addTodo
handlers. These handlers also incorrectly don't store the todos
array.
Use functional state updates and map the existing state todos array to a new array, and copy the todo
item that matches by id so you are not mutating state objects.
Math.random generates floating point numbers, and as such, makes it extremely difficult to compare with ===
. I added a guid to each instead.
class App extends React.Component {
state = {
todos: [
{ id: uuidV4(), content: "Buy milk1", status: "pending" },
{ id: uuidV4(), content: "buy carrots1", status: "pending" },
{ id: uuidV4(), content: "Buy milk2", status: "done" },
{ id: uuidV4(), content: "buy carrots2", status: "deleted" }
]
};
onDelete = (id) => {
this.setState((prevState) => ({
todos: prevState.todos.map((todo) =>
todo.id === id
? {
...todo,
status: "deleted"
}
: todo
)
}));
};
onChangeCheckbox = (id, checked) => {
this.setState((prevState) => ({
todos: prevState.todos.map((todo) =>
todo.id === id
? {
...todo,
status: checked ? "done" : "pending"
}
: todo
)
}));
};
addTodo = (todo) => {
this.setState((prevState) => ({
todos: [
...prevState.todos,
{
...todo,
id: uuidV4(),
status: "pending"
}
]
}));
};
render() {
return (
<div>
<h1>Todo's App</h1>
<AddTodo addTodo={this.addTodo} />
<Todo
todos={this.state.todos}
deleteTodo={this.onDelete}
onChangeCheckbox={this.onChangeCheckbox}
/>
</div>
);
}
}
There is also no need to store any checked
state in Todo.js
as the checked state is easily derived from your todo.status
property. If the status is "done" then check the box.
You can (should) run your todos
array through a filter first to remove the deleted status todos.
const Todo = ({ todos, deleteTodo, onChangeCheckbox }) => {
const todoList = todos.length ? (
todos
.filter(({ status }) => status !== "deleted")
.map((todo) => {
return (
<div key={todo.id}>
<div>
<input
type="checkbox"
checked={todo.status === "done"}
onChange={(event) =>
onChangeCheckbox(todo.id, event.target.checked)
}
/>
<p
style={
todo.status === "pending"
? { color: "red" }
: { color: "green", textDecoration: "line-through" }
}
>
{todo.content}
</p>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</div>
<hr />
</div>
);
})
) : (
<p>You have no todos</p>
);
return <div>{todoList}</div>;
};
Upvotes: 1
Reputation: 71
You have only one checked
state, for every todo
item.
I'd recommend for you to add checked
state to every item in todos
list.
Then you can find item and change state accordingly
Upvotes: 1