javascript - adding onClick functionality to React to do list - Stack Overflow

I've created a to do list with React. I want to be able to mark tasks pleted simply by clicking on

I've created a to do list with React. I want to be able to mark tasks pleted simply by clicking on them. I also want to be able to clear task that have been pleted by clicking a button. These two functions are not working correctly with the code I have set up. When I click on an individual todo item to mark it plete, every single to do item on the list gets marked plete and thus, appears with a 'line-through.' When I then click the designated button to clear pleted tasks, absolutely nothing happens. Can someone help me resolve these two issues?

code from App ponent:

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: [
        {
          task: "learn how to fly drone",
          id: Date.now(),
          pleted: false
        }, 
        {
          task: "learn React class ponents",
          id: Date.now(),
          pleted: false
        },
        {
          task: "practice editing videos",
          id: Date.now(),
          pleted: false
        },
        {
          task: "read Ten Years A Nomad",
          id: Date.now(),
          pleted: false
        }
    ],
      todo: ''
    }
  }

  inputChangeHandler = event => {
    this.setState({[event.target.name]: event.target.value})
  }

  addTask = event => {
    event.preventDefault();
    let newTask = {
      task: this.state.todo,
      id: Date.now(),
      pleted: false
    };
    this.setState({
      todos: [...this.state.todos, newTask],
      todo: ''
    })
  }

  toggleComplete = itemId => {
    const todos = this.state.todos.map(todo => {
      if (todo.id === itemId) {
        todopleted = !todopleted
      }
      return todo
    });
    this.setState({todos, todo: ''})
  }

  clearCompleted = e => {
    e.preventDefault();
    return this.state.todos.filter(item => !itempleted)
  }

  render() {
    return (
      <div className="App">
        <h2>Wele to your Todo App!</h2>
        <TodoList 
          todos={this.state.todos} 
          toggleComplete={this.toggleComplete} />
        <TodoForm todos={this.state.todos} value={this.state.todo} inputChangeHandler={this.inputChangeHandler} addTask={this.addTask} clearCompleted={this.clearCompleted}/>
      </div>
    );
  }
}

export default App;

TodoList:

const TodoList = props => {
    return (
        <div>
            {props.todos.map((todo, id) => (
                <Todo 
                    todo={todo} 
                    key={id} 
                    toggleComplete={props.toggleComplete} />
            ))}
        </div>
    )
}

export default TodoList;

Todo:

const Todo = props => {
    return (
        <div 
            key={props.todo.id}
            onClick={() => {
                props.toggleComplete(props.todo.id)
            }}>
            <p 
                style={{textDecoration: props.todopleted ? 'line-through' : 'none'}}>
                {props.todo.task}
            </p>
        </div>
    )
}

export default Todo;

TodoForm:

const TodoForm = props => {
    return (
        <form>
            <input 
                name="todo" 
                value={props.value} 
                type="text" 
                onChange={props.inputChangeHandler} 
                placeholder="Enter new task" />
            <button onClick={props.addTask}>Add Todo</button>
            <button onClick={props.clearCompleted}>Clear Completed</button>
        </form>
    )
}

export default TodoForm;

I've created a to do list with React. I want to be able to mark tasks pleted simply by clicking on them. I also want to be able to clear task that have been pleted by clicking a button. These two functions are not working correctly with the code I have set up. When I click on an individual todo item to mark it plete, every single to do item on the list gets marked plete and thus, appears with a 'line-through.' When I then click the designated button to clear pleted tasks, absolutely nothing happens. Can someone help me resolve these two issues?

code from App ponent:

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: [
        {
          task: "learn how to fly drone",
          id: Date.now(),
          pleted: false
        }, 
        {
          task: "learn React class ponents",
          id: Date.now(),
          pleted: false
        },
        {
          task: "practice editing videos",
          id: Date.now(),
          pleted: false
        },
        {
          task: "read Ten Years A Nomad",
          id: Date.now(),
          pleted: false
        }
    ],
      todo: ''
    }
  }

  inputChangeHandler = event => {
    this.setState({[event.target.name]: event.target.value})
  }

  addTask = event => {
    event.preventDefault();
    let newTask = {
      task: this.state.todo,
      id: Date.now(),
      pleted: false
    };
    this.setState({
      todos: [...this.state.todos, newTask],
      todo: ''
    })
  }

  toggleComplete = itemId => {
    const todos = this.state.todos.map(todo => {
      if (todo.id === itemId) {
        todo.pleted = !todo.pleted
      }
      return todo
    });
    this.setState({todos, todo: ''})
  }

  clearCompleted = e => {
    e.preventDefault();
    return this.state.todos.filter(item => !item.pleted)
  }

  render() {
    return (
      <div className="App">
        <h2>Wele to your Todo App!</h2>
        <TodoList 
          todos={this.state.todos} 
          toggleComplete={this.toggleComplete} />
        <TodoForm todos={this.state.todos} value={this.state.todo} inputChangeHandler={this.inputChangeHandler} addTask={this.addTask} clearCompleted={this.clearCompleted}/>
      </div>
    );
  }
}

export default App;

TodoList:

const TodoList = props => {
    return (
        <div>
            {props.todos.map((todo, id) => (
                <Todo 
                    todo={todo} 
                    key={id} 
                    toggleComplete={props.toggleComplete} />
            ))}
        </div>
    )
}

export default TodoList;

Todo:

const Todo = props => {
    return (
        <div 
            key={props.todo.id}
            onClick={() => {
                props.toggleComplete(props.todo.id)
            }}>
            <p 
                style={{textDecoration: props.todo.pleted ? 'line-through' : 'none'}}>
                {props.todo.task}
            </p>
        </div>
    )
}

export default Todo;

TodoForm:

const TodoForm = props => {
    return (
        <form>
            <input 
                name="todo" 
                value={props.value} 
                type="text" 
                onChange={props.inputChangeHandler} 
                placeholder="Enter new task" />
            <button onClick={props.addTask}>Add Todo</button>
            <button onClick={props.clearCompleted}>Clear Completed</button>
        </form>
    )
}

export default TodoForm;
Share Improve this question asked Dec 27, 2019 at 2:10 Jevon CochranJevon Cochran 1,7842 gold badges16 silver badges30 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

1) The reason why every item is marked is because all objects within the todos state has the same id. Therefore, toggleComplete() will end up marking all objects within todos as true.

What you can do is to assign each object with a unique id, instead of assigning all id with the same Date object.

Here is an example:

constructor() {
  super();
  this.state = {
    todos: [
      {
        task: "learn how to fly drone",
        id: 1,
        pleted: false
      }, 
      {
        task: "learn React class ponents",
        id: 2,
        pleted: false
      },
      {
        task: "practice editing videos",
        id: 3,
        pleted: false
      },
      {
        task: "read Ten Years A Nomad",
        id: 4,
        pleted: false
      }
  ],  
    todo: ''
  }
}

2) Next, clearCompleted is not calling setState(), hence, none of the tasks are cleared. I assume that you are trying to set pleted as false? In that case, you can simply do set pleted as false for all objects, and then update your state.

clearCompleted = e => {
  e.preventDefault();
  const todos = this.state.todos.map(todo => ({
    ...todo,
    pleted: false,
  }));

  this.setState({
    todos,
  });
}

I have created a demo which fixes your issues.

Edit:

@wentjun's answer should be selected as the accepted answer.. I am going to leave this answer up though, as I still feel it provides value. To elaborate: I prefer to pass the index as it's a little faster then having to map over every single todo item just to find the one that was clicked. (Even if you used this.state.todos.find(itm => itm.id === itemId, passing the index is faster since you already know which item was clicked..


Original Answer:

I modified the ToDo ponent to pass the entire todo object in the click event, as well as passing the todo item index as well as the todo item within the ToDoList ponent. This way you can grab the index of the todo item that was clicked, to easily change the pleted property on that specific todo item within state.

Even though I am not doing anything with the todo item object that is being passed via the click event, I remend still passing the entire object, just in case - makes things more flexible. Open up your console to see the todo object that gets clicked.

Edit: I updated the clearCompleted to:

  clearCompleted = e => {
    e.preventDefault();
    let stateCopy = {...this.state};
    stateCopy.todos = stateCopy.todos.reduce((acc, cur) => 
      [...acc, {...cur, pleted: false}], []
    );
    this.setState(stateCopy);
  };

I also modified your setState call within the click handler.. It is best practice to always make a copy of your state, modify the copy, then update state with the copy.

Demo:

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: [
        {
          task: "learn how to fly drone",
          id: Date.now(),
          pleted: false
        },
        {
          task: "learn React class ponents",
          id: Date.now(),
          pleted: false
        },
        {
          task: "practice editing videos",
          id: Date.now(),
          pleted: false
        },
        {
          task: "read Ten Years A Nomad",
          id: Date.now(),
          pleted: false
        }
      ],
      todo: ""
    };
  }

  inputChangeHandler = event => {
    this.setState({ [event.target.name]: event.target.value });
  };

  addTask = event => {
    event.preventDefault();
    let newTask = {
      task: this.state.todo,
      id: Date.now(),
      pleted: false
    };
    this.setState({
      todos: [...this.state.todos, newTask],
      todo: ""
    });
  };

  toggleComplete = (todoItem, todoItemIndex) => {
    let stateCopy = { ...this.state };
    let item = stateCopy.todos[todoItemIndex];
    item.pleted = !item.pleted;
    this.setState(stateCopy, () => 
      console.log(this.state.todos[todoItemIndex])
    );
  };

  clearCompleted = e => {
    e.preventDefault();
    let stateCopy = {...this.state};
    stateCopy.todos = stateCopy.todos.reduce((acc, cur) => 
      [...acc, {...cur, pleted: false}], []
    );
    this.setState(stateCopy);
  };

  render() {
    return (
      <div className="App">
        <h2>Wele to your Todo App!</h2>
        <TodoList
          todos={this.state.todos}
          toggleComplete={this.toggleComplete}
        />
        <TodoForm
          todos={this.state.todos}
          value={this.state.todo}
          inputChangeHandler={this.inputChangeHandler}
          addTask={this.addTask}
          clearCompleted={this.clearCompleted}
        />
      </div>
    );
  }
}

const TodoList = ({ todos, toggleComplete }) => {
  return (
    <div>
      {todos && todos.map((todo, index) => (
        <Todo
          todo={todo} 
          key={index} 
          toggleComplete={() => toggleComplete(todo, index)} /* <<--- Pass the item and index to the handler function */ 
        />                                                   /* Even though we are not using the actual todo item     */
                                                             /* object, still not a bad idea to pass it thru          */ 
        
      ))}
    </div>
  );
};

const Todo = props => {
  return (
    <div 
      key={props.todo.id} 
      onClick={() => { props.toggleComplete(props.todo) }}> {/* Pass entire todo object, just in case you need it */}
      <p 
        style={{ 
          cursor: 'pointer',
          textDecoration: props.todo.pleted ? "line-through" : "none" 
        }}>
        {props.todo.task}
      </p>
    </div>
  );
};

const TodoForm = props => {
  return (
    <form>
      <input
        name="todo"
        value={props.value}
        type="text"
        onChange={props.inputChangeHandler}
        placeholder="Enter new task"
      />
      <button onClick={props.addTask}>Add Todo</button>
      <button onClick={props.clearCompleted}>Clear Completed</button>
    </form>
  );
};


ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744795880a4594199.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信