skip to Main Content

I am trying to mark a targeted todo as done, but instead when i press and todo as done, all the list are marked done which im trying to figure it out why, can anyone explain to me what im doing wrong ? I’m still a beginner and try to understand such behaviors.

The function handleDoneTask was suppose to run for each todo and when i press done it should be mark that specific todo in a list as done task

import { useState } from "react";
import "./App.scss";

function App() {
  //Value of Input
  const [val, setVal] = useState("");
  const [todos, setTodos] = useState([]);
  const [doneTask, setDoneTast] = useState(false);

  const addTodos = (e) => {
    if (val == "") return;
    e.preventDefault();
    setTodos([...todos, val]);
    setVal("");
  };

  const handleDelete = (id) => {
    setTodos(todos.filter((_, key) => key !== id));
  };

  const handleDoneTask = (id) => {
    const DoneArray = [...todos];
    DoneArray.map((_, key) => {
      if (id === key) {
        setDoneTast(true);
      }
    });
  };

  
  return (
    <div className="App">
      {/** On change values*/}
      <form onSubmit={addTodos}>
        <input
          onChange={(e) => setVal(e.target.value)}
          value={val}
          type="text"
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map((todo, key) => (
          <div className="arrange" key={key}>
            <li className={doneTask ? "doneTask" : null}>{todo}</li>
            <a onClick={() => handleDoneTask(key)}>Done</a>
            <a onClick={() => handleDelete(key)}>Cancel</a>
          </div>
        ))}
      </ul>
    </div>
  );
}

export default App;


//css
.doneTask {
   text-decoration: line-through!important;

}

3

Answers


  1. Because all your tasks depend on one state doneTask which will be true/false for all tasks.

    A solution you split the task element in it’s own component and move the state to the task component.

    import { useState } from "react";
    import "./App.scss";
    
    function Task({ todo, handleDelete, key }) {
      
      const [doneTask, setDoneTast] = useState(false);
    
      const handleDoneTask = (id) => {
            setDoneTast(true);
      };
      return <div className="arrange">
                <li className={doneTask ? "doneTask" : null}>{todo}</li>
                <a onClick={() => handleDoneTask(key)}>Done</a>
                <a onClick={() => handleDelete(key)}>Cancel</a>
              </div>
    }
    
    function App() {
      //Value of Input
      const [val, setVal] = useState("");
      const [todos, setTodos] = useState([]);
    
      const addTodos = (e) => {
        if (val == "") return;
        e.preventDefault();
        setTodos([...todos, val]);
        setVal("");
      };
    
      const handleDelete = (id) => {
        setTodos(todos.filter((_, key) => key !== id));
      };
    
      
      return (
        <div className="App">
          {/** On change values*/}
          <form onSubmit={addTodos}>
            <input
              onChange={(e) => setVal(e.target.value)}
              value={val}
              type="text"
            />
            <button type="submit">Add</button>
          </form>
          <ul>
            {todos.map((todo, key) => (
              <Task todo={todo} key={key} handleDelete={handleDelete} />
            ))}
          </ul>
        </div>
      );
    }
    
    export default App;
    
    Login or Signup to reply.
  2. You can create a separate component to represent a Todo which maintains its own state.

    Note that <div> is not valid as a direct child of an unordered list. You can, however, put it inside a list item.

    function Todo({ name, onCancel }) {
      const [doneTask, setDoneTask] = useState(false);
      return (
        <li className="arrange">
          <span className={doneTask ? "doneTask" : null}>{name}</span>
          <div>
            <a onClick={() => setDoneTask(true)}>Done</a>{" "}
            <a onClick={onCancel}>Cancel</a>
          </div>
        </li>
      );
    }
    
    function App() {
      const [val, setVal] = useState("");
      const [todos, setTodos] = useState([]);
      const addTodos = (e) => {
        if (val === "") return;
        e.preventDefault();
        setTodos([...todos, val]);
        setVal("");
      };
      const handleDelete = (idx) => {
        setTodos(todos.filter((_, i) => i !== idx));
      };
      return (
        <div className="App">
          <form onSubmit={addTodos}>
            <input
              onChange={(e) => setVal(e.target.value)}
              value={val}
              type="text"
            />
            <button type="submit">Add</button>
          </form>
          <ul>
            {todos.map((todo, i) => (
              <Todo name={todo} onCancel={() => handleDelete(i)} />
            ))}
          </ul>
        </div>
      );
    }
    
    Login or Signup to reply.
  3. If you maintain an array of objects each with their own id, text, and done status you’ll find it much easier to maintain your state. Adding the id to each list item as a data attributes means that you can manipulate the todo objects in state more easily.

    In addition if you split up your code into separate components you can have your Form component responsible for the state of the input element, and just have Todo as a dumb component that just accepts and displays data.

    const { useState } = React;
    
    function Example() {
    
      // One state for the todos (an array of objects)
      const [todos, setTodos] = useState([]);
    
      // When a new todo is added we add the text in
      // an object along with a unique id, and a done status
      function handleAdd(text) {
        setTodos([
          ...todos,
          { id: todos.length + 1, text, done: false }
        ]);
      }
    
      // When a delete button is clicked find the closest
      // list item it's within, then grab the id from the dataset,
      // and use that id to filter out the desired object
      function handleDelete(e) {
        const item = e.target.closest('li');
        const { dataset: { id } } = item;
        const filtered = todos.filter(todo => {
          return todo.id !== Number(id);
        });
        setTodos(filtered);
      }
    
      // Similarly to "delete" when a "done" button
      // is clicked find the closest list item it's within,
      // then grab the id from the dataset, and use that id
      // to change the done status of the desired object
      function handleDone(e) {
        const item = e.target.closest('li');
        const { dataset: { id } } = item;
        const mapped = todos.map(todo => {
          if (todo.id === Number(id)) {
            return { ...todo, done: true };
          }
          return todo;
        });
        setTodos(mapped);
      };
    
      // Have separate components for the Form, the
      // list of Todos (in case you want to style that
      // differently), and each Todo, passing down the
      // relevant handlers to each
      return (
        <div className="App">
          <Form handleAdd={handleAdd} />
          <TodoList>
            {todos.map(todo => {
              return (
                <Todo
                  key={todo.id}
                  data={todo}
                  handleDone={handleDone}
                  handleDelete={handleDelete}
                />
              );
            })}
          </TodoList>
        </div>
      );
    }
    
    // Form handles its own input state.
    // When the add button is clicked it calls the
    // handleAdd function passed to it in props
    function Form({ handleAdd }) {
      
      const [text, setText] = useState('');
    
      function handleInput(e) {
        setText(e.target.value);
      }
    
      function handleButton(e) {
        handleAdd(text);
        setText('');
      }
      
      return (
        <section className="form">
          <input
            onChange={handleInput}
            value={text}
            type="text"
          />
          <button
            type="button"
            onClick={handleButton}
          >Add
          </button>
        </section>
      );
    
    }
    
    // TodoList displays a list of todos
    function TodoList({ children }) {
      return (
        <ul className="todolist">
          {children}
        </ul>
      );
    }
    
    // Each todo is handed a set of data (id, text, done status),
    // and a couple of handlers. Depending on the status of done
    // we can strike out the todo in the HTML
    function Todo(props) {
      
      const {
        data: { id, text, done },
        handleDone,
        handleDelete
      } = props;
      
      const cn = ['text', done && 'strike'].join(' ');
      
      return (
        <li data-id={id}>
          <span className={cn}>{text}</span>
          <section className="buttons">
            <button
              className="done"
              type="button"
              onClick={handleDone}
            >Done</button>
            <button
              className="delete"
              type="button"
              onClick={handleDelete}
            >Delete
            </button>
          </section>
        </li>
      );
    
    }
    
    ReactDOM.render(
      <Example />,
      document.getElementById('react')
    );
    .done:hover, .delete:hover { cursor: pointer; }
    .delete { background-color: salmon; }
    .done { margin: 0 0.5em; background-color: lightgreen; }
    .strike { text-decoration: line-through; }
    .todolist { list-style: none; border: 1px solid lightgray; padding: 1em; width: 80%; }
    .todolist li { display: flex; flex-direction: row; width: 100%; padding: 0.25em;}
    .todolist li:not(:last-child) { margin-bottom: 0.25em; }
    .todolist li .buttons { margin-left: auto; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search