最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin5浏览0评论

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>

发布评论

评论列表(0)

  1. 暂无评论