将onClick功能添加到React的待办事项列表中

问题描述 投票:1回答:2

我已经用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;

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.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
2个回答
3
投票

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?在这种情况下,您只需将所有对象的完成设置为false,然后更新您的状态即可。

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

  this.setState({
    todos,
  });
}

我创建了一个demo,可以解决您的问题。


1
投票

编辑:

@wentjun's answer应该被选择为可接受的答案。。尽管我仍然觉得它提供了价值,但我还是放弃了这个答案。详细说明:我更喜欢传递索引,因为索引要快一些,因此只需对每个待办事项进行map,即可找到被单击的项目。 (即使您使用了this.state.todos.find(itm => itm.id === itemId,传递索引也会更快,因为您已经知道单击了哪个项目。]


原始答案:

我修改了ToDo组件,以在click事件中传递整个待办事项对象,并在ToDoList组件内传递待办事项索引以及待办事项。这样,您可以获取被单击的待办事项的索引,以轻松更改状态内该特定待办事项的completed属性。

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

编辑:我将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>
© www.soinside.com 2019 - 2024. All rights reserved.