【问题标题】:adding onClick functionality to React to do list将 onClick 功能添加到 React 待办事项列表
【发布时间】:2020-04-17 02:25:00
【问题描述】:

我用 React 创建了一个待办事项列表。我希望能够通过单击它们来标记已完成的任务。我还希望能够通过单击按钮清除已完成的任务。这两个功能无法与我设置的代码一起正常工作。当我单击单个待办事项以将其标记为已完成时,列表中的每个待办事项都会被标记为已完成,因此显示为“直通”。当我然后单击指定按钮清除已完成的任务时,绝对没有任何反应。有人可以帮我解决这两个问题吗?

App 组件代码:

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: [
        {
          task: "learn how to fly drone",
          id: Date.now(),
          completed: false
        }, 
        {
          task: "learn React class components",
          id: Date.now(),
          completed: false
        },
        {
          task: "practice editing videos",
          id: Date.now(),
          completed: false
        },
        {
          task: "read Ten Years A Nomad",
          id: Date.now(),
          completed: 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(),
      completed: false
    };
    this.setState({
      todos: [...this.state.todos, newTask],
      todo: ''
    })
  }

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

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

  render() {
    return (
      <div className="App">
        <h2>Welcome 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;

待办事项列表:

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

export default TodoList;

待办事项:

const Todo = props => {
    return (
        <div 
            key={props.todo.id}
            onClick={() => {
                props.toggleComplete(props.todo.id)
            }}>
            <p 
                style={{textDecoration: props.todo.completed ? '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;

【问题讨论】:

    标签: javascript arrays reactjs onclick


    【解决方案1】:

    1) 之所以标记每个项目是因为todos 状态内的所有对象都具有相同的id。因此,toggleComplete() 最终会将todos 中的所有对象标记为true

    您可以为每个对象分配一个唯一的id,而不是为所有id 分配相同的Date 对象。

    这是一个例子:

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

    2) 接下来,clearCompleted 没有调用setState(),因此,没有任何任务被清除。我假设您正在尝试将completed 设置为假?在这种情况下,您可以简单地将所有对象设置为 false,然后更新您的状态。

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

    我创建了一个demo 来解决您的问题。

    【讨论】:

    • 不错的答案!我匆忙回答并忽略了..现在很明显...这应该被选为接受的答案。
    • @MattOestreich 很高兴为您提供帮助!随意查看我创建的演示。问题应该解决了:)
    • 谢谢。我能够根据您的回答解决问题。您在演示中使用的 clearCompleted 功能不仅仅供参考。我通过这样做使它工作... clearCompleted = e => { e.preventDefault();常量 todos = this.state.todos.filter(item => !item.completed); this.setState({ ...this.state, todos }) }
    【解决方案2】:

    编辑:

    @wentjun's answer 应该被选为接受的答案。不过,我将保留这个答案,因为我仍然觉得它提供了价值。详细说明:我更喜欢传递索引,因为它比必须在每个待办事项上 map 来找到被点击的那个要快一些。 (即使您使用this.state.todos.find(itm =&gt; itm.id === itemId,传递索引也会更快,因为您已经知道点击了哪个项目..


    原答案:

    我修改了ToDo 组件以在click 事件中传递整个todo 对象,以及传递todo 项索引以及ToDoList 组件中的todo 项。通过这种方式,您可以获取被点击的待办事项的索引,从而轻松更改状态中特定待办事项的completed 属性。

    即使我没有对通过 click 事件传递的待办事项对象做任何事情,我还是建议传递整个对象,以防万一 - 使事情更加灵活。打开控制台查看被点击的 todo 对象。

    编辑:我将clearCompleted 更新为:

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

    我还在点击处理程序中修改了您的 setState 调用。最佳做法是始终复制您的状态,修改副本,然后使用副本更新状态。

    演示:

    class App extends React.Component {
      constructor() {
        super();
        this.state = {
          todos: [
            {
              task: "learn how to fly drone",
              id: Date.now(),
              completed: false
            },
            {
              task: "learn React class components",
              id: Date.now(),
              completed: false
            },
            {
              task: "practice editing videos",
              id: Date.now(),
              completed: false
            },
            {
              task: "read Ten Years A Nomad",
              id: Date.now(),
              completed: 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(),
          completed: false
        };
        this.setState({
          todos: [...this.state.todos, newTask],
          todo: ""
        });
      };
    
      toggleComplete = (todoItem, todoItemIndex) => {
        let stateCopy = { ...this.state };
        let item = stateCopy.todos[todoItemIndex];
        item.completed = !item.completed;
        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, completed: false}], []
        );
        this.setState(stateCopy);
      };
    
      render() {
        return (
          <div className="App">
            <h2>Welcome 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.completed ? "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.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 2021-05-09
      • 2022-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多