Checkboxes are not working in todo in react js

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

3 answers

  • answered 2020-10-16 04:43 mTdev1

    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

  • answered 2020-10-16 05:39 Drew Reese

    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>;
    };
    

    Edit checkboxes-are-not-working-in-todo-in-react-js

  • answered 2020-10-16 06:00 samehanwar

    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