skip to Main Content

As you can see from the code, at first I want all the todos to be shown, then toggle between all, active and completed todos. I have made the arrays for the active and completed todos, but I’m not quite sure how to implement the switching between those lists. For example, how can I render only active todos instead of the all the todos, etc.?

return (
    <div className='card'>
      <TodoForm addTodo={addTodo} />
      <div className='list-group'>
              {
            todos.map((item, index) =>
              item.isEditing ? <TodoEditForm item={item} key={index} editTask={editTask} /> :
                <Todo item={item} key={index}
                  toggleCompleted={toggleCompleted}
                  removeTodo={removeTodo}
                  toggleEdit={toggleEdit} />
            )
              }

          {
            bool ? 
            <div>
              Active
            </div> 
            :
            <div>
              Completed
            </div>
          }
      </div>
      <div className='toggle-todos-wrapper'>
        <button className="toggle-todos-button btn btn-light">All</button>
        <button className="toggle-todos-button btn btn-light">Active</button>
        <button className="toggle-todos-button btn btn-light">Completed</button>
      </div>
    </div>
  )
}

export default TodoWrapper

I tried using the ternary operator to switch between the active and completed todos, but the all todos remain.

2

Answers


  1. As the code snippet is partial, I am not sure how are you exactly making the arrays for active and completed todos. Therefore, I will assume that you have something like this in your code:

       /*Existing imports*/       
       const allTodos = [...allTodos]; //Array containing all Todos
       const activeTodos = [...activeTodos]; //Array containing all 'active' Todos
       const completedTodos = [...completedTodos]; //Array containing all 'completed' Todos    
       function TodoWrapper({...props}){
            /*Existing code*/
            return(
          /*Your return logic posted above*/
        )
       }
    

    Now either you are getting arrays from props or generating dynamically the following approach should work the same. To address the conditional rendering, you might need to change your existing code a bit. First of all instead of mapping over the todos array inside return JSX, move that logic to a re-usable function. Secondly, your ternary operator can work but currently you are using a bool variable, which I assume is a boolean variable, to determine the kind of todos to render. If you want to show 3 different kinds of Todos, then you shall change the bool to local state. And might set its type to an enum or constant array. But I will use strings for simplicity.

    The following changes shall get your code to conditionally toggle between the 3 types of Todos rendering:

    /*Existing imports*/       
       const allTodos = [...allTodos]; //Array containing all Todos
       const activeTodos = [...activeTodos]; //Array containing all 'active' Todos
       const completedTodos = [...completedTodos]; //Array containing all 'completed' Todos    
       function TodoWrapper({...props}){
            /*Existing code*/
            const [todoType,setTodoType] = useState('all'); //You can use Enum or constants as it is going to have only one of the three different values at any time
            const handleTodoTypeChange = (type) => () => {
              setTodoType(tpye); // type can either be 'all', 'active', or 'completed'
            };
            const renderTodos = () => {
             let filteredTodos = allTodos;
             if(todoType === "active") filteredTodos = activeTodos;
             else if(todoType === "completed") filteredTodos = completedTodos;
             const list =filteredTodos.map((item, index) =>
                  item.isEditing ? <TodoEditForm item={item} key={index} editTask={editTask} /> :
                    <Todo item={item} key={index}
                      toggleCompleted={toggleCompleted}
                      removeTodo={removeTodo}
                      toggleEdit={toggleEdit} />
                );
             return list;
            }
            return (
                <div className='card'>
                  <TodoForm addTodo={addTodo} />
                  <div className='list-group'>
                  {
                    renderTodos() 
                  }
                  </div>
                  <div className='toggle-todos-wrapper'>
                    <button className="toggle-todos-button btn btn-light" onClick={handleTodoTypeChange("all")}>All</button>
                    <button className="toggle-todos-button btn btn-light" onClick={handleTodoTypeChange("active")}>Active</button>
                    <button className="toggle-todos-button btn btn-light" onClick={handleTodoTypeChange("completed")}>Completed</button>
                  </div>
            </div>
          )
    }
    export default TodoWrapper
    

    I have removed the ternary logic from your existing code as it is being handled inside the newly created renderTodos function.

    Login or Signup to reply.
  2. You may proceed by introducing a new state variable that acts as a flag telling which category (all, completed or active) is being selected.

    That variable can therefore be used to filter the todos on the fly to display those in the category being selected or all of them when All button is selected.

    Below a code snippet that demponstrates the aforementioned attempt:

    function ToDoList({ todos }) {
      const [category, setCategory] = useState("all"),
        handleClick = e => setCategory(e.target.dataset.completed),
        filtered = category === "all" ? todos : todos.filter(t => t.completed == +category);
      
      return (
        <div className="card">
          <div className="list-group">
            {filtered.map(({ text, completed }) => <li>{`${text}: ${completed ? 'Done' : 'Active'}`}</li>)}
          </div>
          <div className="toggle-todos-wrapper">
            <button
              className="toggle-todos-button btn btn-light"
              onClick={handleClick}
              data-completed="all"
            >All
            </button>
            <button
              className="toggle-todos-button btn btn-light"
              onClick={handleClick}
              data-completed="0"
            >Active
            </button>
            <button
              className="toggle-todos-button btn btn-light"
              onClick={handleClick}
              data-completed="1"
            >Completed
            </button>
          </div>
        </div>
      );
    }
    

    And here’s a live demo on CodePen.

    I hope you get the idea and I strongly advise you to customize it the way you see fit and build upon it based on your requirements. Hope you find that attempt helpful.

    Finally, there might be some tweaks that can be applied as improvements and memorizing the filtering callback between re-renders is one of them especially when the todos set is getting larger. I recommend visiting the Docs to learn more about those potential tweaks.

    Please feel free to ask further questions about the suggested solution and I’ll gladly help you out.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search