Skip to content

Uncaught TypeError: Cannot read property of a todolist

Uncaught TypeError: Cannot read property ‘contains’ of undefined

Uncaught TypeError: Cannot set property ‘display’ of undefined

I am trying to loop through todoList and get the child element. Where I am wrong? The child element is the list item. The list items are dynamically added on adding a todo.

const todoInput = document.querySelector('.todo-input');
const todoList = document.querySelector('.todo-list');
const filterOption = document.querySelector(".filter-todo");
const todoButton = document.querySelector(".todo-button");

todoButton.addEventListener("click", addTodo);
filterOption.addEventListener("click", filterTodo);

function addTodo(e) {
  e.preventDefault();
  console.log("hello");
  //todo div
  const todoDiv = document.createElement("div");
  todoDiv.classList.add("todo");
  //create li
  const newTodo = document.createElement('li');
  newTodo.innerText = todoInput.value;
  newTodo.classList.add('todo-item')
  todoDiv.appendChild(newTodo);
  //check mark button
  const completedButton = document.createElement('button');
  completedButton.innerHTML = '<i class="fas fa-check"></i>';
  completedButton.classList.add("complete-btn");
  todoDiv.appendChild(completedButton);
  //check trash button
  const trashButton = document.createElement('button');
  trashButton.innerHTML = '<i class="fas fa-trash"></i>';
  trashButton.classList.add("trash-btn");
  todoDiv.appendChild(trashButton);

  //append to list
  todoList.appendChild(todoDiv)
  //clear todo input value
  todoInput.value = "";

}

function filterTodo(e) {
  const todos = todoList.childNodes;
  todos.forEach(function(todo) {
    console.log(todo);
    switch (e.target.value) {
      case "all":
        todo.style.display = "flex";
        break;
      case "completed":
        if (todo.classList.contains("completed")) {
          todo.style.display = "flex";
        } else {
          todo.style.display = "none";
        }
        break;
      case "uncompleted":
        if (!todo.classList.contains("completed")) {
          todo.style.display = "flex";
        } else {
          todo.style.display = "none";
        }
    }
  });
}
.todo-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.todo-list {
  min-width: 30%;
  list-style: none;
}

.todo {
  margin: 0.5rem;
  background: white;
  color: black;
  font-size: 1.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: all 0.5s ease;
}

.todo li {
  flex: 1;
}

.trash-btn,
.complete-btn {
  background: #ff6f47;
  color: white;
  border: none;
  padding: 1rem;
  cursor: pointer;
  font-size: 1rem;
}

.complete-btn {
  background: rgb(73, 204, 73);
}

.todo-item {
  padding: 0rem 0.5rem;
}

.fa-trash,
.fa-check {
  pointer-events: none;
}

.completed {
  text-decoration: line-through;
  opacity: 0.5;
}

.fall {
  transform: translateY(8rem) rotateZ(20deg);
  opacity: 0;
}

select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  outline: none;
  border: none;
}

.select {
  margin: 1rem;
  position: relative;
  overflow: hidden;
}

select {
  color: #ff6f47;
  width: 10rem;
  cursor: pointer;
  padding: 1rem;
}

.select::after {
  content: "25BC";
  position: absolute;
  background: #ff6f47;
  top: 0;
  right: 0;
  padding: 1rem;
  pointer-events: none;
  transition: all 0.3s ease;
}

.select:hover::after {
  background: white;
  color: #ff6f47;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />
<form>
  <input type="text" class="todo-input">
  <button class="todo-button" type="submit">
            <i class="fas fa-plus-square"></i>
        </button>
  <div class="select">
    <select name="todos" class="filter-todo">
      <option value="all">All</option>
      <option value="completed">Completed</option>
      <option value="uncompleted">Uncompleted</option>
    </select>
  </div>
</form>
<div class="todo-container">
  <ul class="todo-list">
    <!---Adding todos dynamically-->
  </ul>
</div>

Answer

Quite a bit of stuff to rewrite

I added the completed and delete functionality

const todoInput = document.querySelector('.todo-input');
const todoList = document.querySelector('.todo-list');
const filterOption = document.querySelector(".filter-todo");
const todoButton = document.querySelector(".todo-button");

todoButton.addEventListener("click", addTodo);
filterOption.addEventListener("change", filterTodo);
todoList.addEventListener("click", handleTodo)

function handleTodo(e) {
  const tgt = e.target.closest("button");
  if (tgt) {
    if (tgt.classList.contains("complete-btn")) {
      tgt.closest("div").classList.add("completed");
    } else if (tgt.classList.contains("trash-btn")) {
      tgt.closest("div").remove();
    }
  }
}

function addTodo(e) {
  e.preventDefault();
  console.log("hello");
  //todo div
  const todoDiv = document.createElement("div");
  todoDiv.classList.add("todo");
  //create li
  const newTodo = document.createElement('li');
  newTodo.innerText = todoInput.value;
  newTodo.classList.add('todo-item')
  todoDiv.appendChild(newTodo);
  //check mark button
  const completedButton = document.createElement('button');
  completedButton.innerHTML = '<i class="fas fa-check"></i>';
  completedButton.classList.add("complete-btn");
  todoDiv.appendChild(completedButton);
  //check trash button
  const trashButton = document.createElement('button');
  trashButton.innerHTML = '<i class="fas fa-trash"></i>';
  trashButton.classList.add("trash-btn");
  todoDiv.appendChild(trashButton);

  //append to list
  todoList.appendChild(todoDiv)
  //clear todo input value
  todoInput.value = "";

}

function filterTodo(e) {
  const todos = todoList.querySelectorAll(".todo-list div");
  const val = e.target.value;
  todos.forEach(function(todo) {
    const show = val === "all" ||
      (val === "completed" && todo.classList.contains("completed")) ||
      (val === "uncompleted" && !todo.classList.contains("completed"));
    todo.classList.toggle("hide", !show)

  });
}
.todo-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

.todo-list {
  min-width: 30%;
  list-style: none;
}

.todo {
  margin: 0.5rem;
  background: white;
  color: black;
  font-size: 1.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: all 0.5s ease;
}

.todo li {
  flex: 1;
}

.trash-btn,
.complete-btn {
  background: #ff6f47;
  color: white;
  border: none;
  padding: 1rem;
  cursor: pointer;
  font-size: 1rem;
}

.complete-btn {
  background: rgb(73, 204, 73);
}

.todo-item {
  padding: 0rem 0.5rem;
}

.fa-trash,
.fa-check {
  pointer-events: none;
}

.completed {
  text-decoration: line-through;
  opacity: 0.5;
}

.fall {
  transform: translateY(8rem) rotateZ(20deg);
  opacity: 0;
}

select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  outline: none;
  border: none;
}

.select {
  margin: 1rem;
  position: relative;
  overflow: hidden;
}

select {
  color: #ff6f47;
  width: 10rem;
  cursor: pointer;
  padding: 1rem;
}

.select::after {
  content: "25BC";
  position: absolute;
  background: #ff6f47;
  top: 0;
  right: 0;
  padding: 1rem;
  pointer-events: none;
  transition: all 0.3s ease;
}

.select:hover::after {
  background: white;
  color: #ff6f47;
}

.hide {
  display: none
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />

<form>
  <input type="text" class="todo-input">
  <button class="todo-button" type="submit">
            <i class="fas fa-plus-square"></i>
        </button>
  <div class="select">
    <select name="todos" class="filter-todo">
      <option value="all">All</option>
      <option value="completed">Completed</option>
      <option value="uncompleted">Uncompleted</option>
    </select>
  </div>
</form>
<div class="todo-container">
  <ul class="todo-list">
    <!---Adding todos dynamically-->
  </ul>
</div>