0

When I tap a todo item on a touch screen (which works fine on a PC browser), it correctly marks the item as complete and moves it down in the list, which is the expected behavior and works great. However, when I try to drag the same item to reorder its position, nothing happens. More peculiarly, after attempting to drag it, I can no longer mark the item as complete with a simple tap. The only workaround I've found is to tap on the item and simultaneously move my finger to the side slightly. After doing this, the item can again be marked as complete with a single tap. I have been working on fixing this for the third day and I am feeling hopeless. Any help would be appreciated. Below is the code in question:

import React, { useState, useEffect } from 'react';
import TodoForm from './TodoForm';
import Todo from './Todo';
import Header from './Header';
import Dice from './Dice'; // Import the Dice component
import { FaSave } from 'react-icons/fa'; // Import FaSave icon

function TodoList() {
  const [todos, setTodos] = useState(() => {
    try {
      const savedTodos = localStorage.getItem('todos');
      return savedTodos ? JSON.parse(savedTodos) : [];
    } catch (error) {
      console.error('Failed to retrieve todos from localStorage:', error);
      return []; // Return an empty array if parsing fails
    }
  });
  const [animationOn, setAnimationOn] = useState(true);

  const addTodo = todo => {
    if (!todo.text || /^\s*$/.test(todo.text)) {
      return;
    }

    const newTodos = [todo, ...todos];
    setTodos(newTodos);
  };

  const updateTodo = (todoId, newValue) => {
    if (!newValue.text || /^\s*$/.test(newValue.text)) {
      return;
    }

    setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item)));
  };

  const removeTodo = id => {
    // Confirm before removing
    const confirmDelete = window.confirm("Are you sure you want to delete this task?");
    if (confirmDelete) {
      const updatedCurrentTodos = todos.filter(todo => todo.id !== id);
      setTodos(updatedCurrentTodos);
    }
  };
  

  const completeTodo = id => {
    let updatedTodos = todos.map(todo => {
      if (todo.id === id) {
        return { ...todo, isComplete: !todo.isComplete };
      }
      return todo;
    });

    updatedTodos.sort((a, b) => {
      if (a.isComplete && !b.isComplete) return 1;
      if (!a.isComplete && b.isComplete) return -1;
      return 0;
    });

    setTodos(updatedTodos);
  };

  useEffect(() => {
    localStorage.setItem('todos', JSON.stringify(todos));
  }, [todos]); // This effect runs whenever the todos change

  useEffect(() => {
    const savedTodos = JSON.parse(localStorage.getItem('todos'));
    if (savedTodos) {
      setTodos(savedTodos);
    }
  }, []); // This effect runs only once when the component mounts

  const saveTodosAsList = () => {
    if (todos.length === 0) {
      alert("Cannot save an empty todo list.");
      return;
    }

    const currentSavedLists = JSON.parse(localStorage.getItem('savedTodoLists') || '[]');
    const newList = {
      timestamp: new Date().toISOString(),
      todos: [...todos]
    };

    localStorage.setItem('savedTodoLists', JSON.stringify([...currentSavedLists, newList]));
  };

  const handleRandomTask = (selectedTask) => {
    const newTodos = todos.filter(todo => todo.id !== selectedTask.id);
    newTodos.unshift(selectedTask);
    setTodos(newTodos);
    alert(`Your random task is: ${selectedTask.text}`);
  };

  const [draggedTodoId, setDraggedTodoId] = useState(null);
  const [touchStartY, setTouchStartY] = useState(0);
  const [touchEndY, setTouchEndY] = useState(0);


  const onDragStart = (e, todoId) => {
    setDraggedTodoId(todoId);
    e.dataTransfer.setData('text/plain', ''); // for Firefox compatibility
  };

  const onDragOver = (e) => {
    e.preventDefault();
  };

  const onDrop = (e, targetTodoId) => {
    e.preventDefault();
    const draggedTodoIndex = todos.findIndex(todo => todo.id === draggedTodoId);
    const targetTodoIndex = todos.findIndex(todo => todo.id === targetTodoId);

    const newTodos = [...todos];
    const [reorderedTodo] = newTodos.splice(draggedTodoIndex, 1);
    newTodos.splice(targetTodoIndex, 0, reorderedTodo);

    setTodos(newTodos);
    setDraggedTodoId(null);
  };

  const onTouchStart = (e, todoId) => {
    setDraggedTodoId(todoId);
    setTouchStartY(e.touches[0].clientY);
    e.target.style.opacity = '0.6';  // Optional: visual feedback for touch start

  };

  const onTouchMove = (e) => {
  setTouchEndY(e.touches[0].clientY);  // Continuously update the end position during movement
  const movementThreshold = 5;  // Small threshold to differentiate between taps and intended drags
  if (Math.abs(touchEndY - touchStartY) > movementThreshold) {
    e.target.style.opacity = '1';  // Reset opacity or any other visual feedback if it's a drag
    e.preventDefault();  // Prevents scrolling and other default actions
  }
};

  const onTouchEnd = (e, todoId) => {
    e.preventDefault();
    const distanceMoved = touchEndY - touchStartY;  // Calculate total distance moved

    if (distanceMoved < 10) { // If the distance moved is small, treat it as a tap
      completeTodo(todoId);
    } else if (draggedTodoId !== null) {
      const draggedTodoIndex = todos.findIndex(todo => todo.id === draggedTodoId);
      const targetTodoIndex = todos.findIndex(todo => todo.id === todoId);

      if (draggedTodoIndex !== -1 && targetTodoIndex !== -1) {
        const newTodos = [...todos];
        const [reorderedTodo] = newTodos.splice(draggedTodoIndex, 1);
        newTodos.splice(targetTodoIndex, 0, reorderedTodo);

        setTodos(newTodos);
        
      }
    }
    setDraggedTodoId(null);  // Clear the draggedTodoId

  };



  return (
    <>
      <Header isAnimationOn={animationOn} toggleAnimation={() => setAnimationOn(!animationOn)} />
      <div className="todo-app">
        <h1>Mission: Task Possible!</h1>
        <TodoForm onSubmit={addTodo} />
        <div className="buttons-container">
          <Dice todos={todos} onRandomSelect={handleRandomTask} />
          <button onClick={saveTodosAsList} className="save-todo-list-button">
            <FaSave />
          </button>
          
          
        </div>
        {todos.map((todo, index) => (
         <div
         key={todo.id}
         className={index === 0 && animationOn ? 'first-todo' : ''}
         draggable
         onDragStart={(e) => onDragStart(e, todo.id)}
         onDragOver={onDragOver}
         onDrop={(e) => onDrop(e, todo.id)}
         onTouchStart={(e) => onTouchStart(e, todo.id)}
         onTouchMove={onTouchMove}
         onTouchEnd={(e) => onTouchEnd(e, todo.id)}
       >
            
            <Todo
              todo={todo}
              completeTodo={completeTodo}
              removeTodo={removeTodo}
              updateTodo={updateTodo}
            />
          </div>
        ))}
      </div>
    </>
  );
}

export default TodoList;

I wanted the todo items to be draggable, which works fine when tested on a PC browser or as a Progressive Web App (PWA) on a PC. Sadly, it doesn't work as well on touch screens. Drag and drop functionality is okay, but tapping to mark a todo item as completed does not function properly.

0