'React: filtering a todo list based on button clicked

I'm new to React and currently working on a to-do list app. Currently, I'm able to add, delete and edit the to-do list.

I have a problem filtering my to-do list based on categories. The categories I have are all, active and completed.

I'm stuck trying to filter the selected list based on the button clicked.

App.js

import React from "react";
import "./styles.css";
import "./App.css";
import Header from "./components/Header";
import AddTask from "./components/AddTask";
import Task from "./components/Task";
import Filterbtns from "./components/Filterbtns";
import data from "./data";
import { nanoid } from "nanoid";

const FILTER_MAP = {
  All: () => true,
  Active: (todo) => !todo.completed,
  Completed: (todo) => todo.completed
};

const FILTER_NAMES = Object.keys(FILTER_MAP); //keys

function App() {
  const [taskList, setTaskList] = React.useState(data);
  const [filtered, setFiltered] = React.useState(data); //state to be filtered

  const filteredListName = FILTER_NAMES;

  const [activeList, setActiveList] = React.useState(filteredListName[0]); //default list

  const taskItems = filtered.map((todo) => {
    return (
      <Task
        id={todo.id}
        name={todo.name}
        completed={todo.completed}
        key={todo.id}
        toggleTaskCompleted={toggleTaskCompleted}
        deleteTask={deleteTask}
        editTask={editTask}
      />
    );
  });

  const taskNoun = taskList.length !== 1 ? "tasks" : "task";
  const headingText = `${taskList.length} ${taskNoun} remaining`;

  function toggleTaskCompleted(id) {
    const updatedTasks = taskList.map((todo) => {
      if (id === todo.id) {
        return { ...todo, completed: !todo.completed };
      }
      return todo;
    });
    setTaskList(updatedTasks);
  }

  function addTask(name) {
    const newTask = { id: nanoid(), name: name, completed: false };
    setTaskList([...taskList, newTask]);
  }

  function deleteTask(id) {
    const remTasks = taskList.filter((todo) => id !== todo.id);
    setTaskList(remTasks);
  }

  function editTask(id, newName) {
    const editTaskList = taskList.map((todo) => {
      if (id === todo.id) {
        return { ...todo, name: newName };
      }
      return todo;
    });
    setTaskList(editTaskList);
  }

  return (
    <div className="App">
      <Header />
      <AddTask addTask={addTask} />
      <div>
        <div className="task--list-btn">
          <Filterbtns
            taskList={taskList}
            setFiltered={setFiltered}
            filteredListName={filteredListName}
            activeList={activeList}
            setActiveList={setActiveList}
          />

          <div className="task--lst">
            <h2>TASKS</h2>
            <h3>{headingText}</h3>
            {taskItems}
          </div>
        </div>
        <div>No task Available</div>
      </div>
    </div>
  );
}

export default App

Filterbtns.js

import React from "react";

export default function Filterbtns(props) {
  React.useEffect(() => {
    if (props.activeList) {
      props.setActiveList(props.filteredListName[0]);
      console.log("try");
      return;
    }
    const filtered = props.taskList.filter((todo) =>
      todo.includes(props.activeList)
    );
    props.setFiltered(filtered);
  }, [props.activeList]);

  return (
    <div className="task--btns">
      <button
        className="all-tasks inputs"
        onClick={() => props.setActiveList(props.FilterbtnsfilteredListName[0])}
      >
        ALL
      </button>
      <br />
      <button
        className="active-tasks inputs"
        onClick={() => props.setActiveList(props.filteredListName[1])}
      >
        ACTIVE
      </button>
      <br />
      <button
        className="completed-tasks inputs"
        onClick={() => props.setActiveList(props.filteredListName[2])}
      >
        COMPLETED
      </button>
    </div>
  );
}


Solution 1:[1]

I've not checked but from what it looks like React.useEffect is redundant inside Filterbtns and you need to pass down FilterbtnsfilteredListName to Filterbtns as props like this:

<Filterbtns
  taskList={taskList}
  setFiltered={setFiltered}
  filteredListName={filteredListName}
  activeList={activeList}
  setActiveList={setActiveList}
  FilterbtnsfilteredListName={filteredListName} // you forgot this
/>

Although if I can change the logic a bit, a better composition would be:

const FILTER_MAP = {
  All: () => true,
  Active: (todo) => !todo.completed,
  Completed: (todo) => todo.completed
};

const FILTER_NAMES = Object.keys(FILTER_MAP); //keys

export default function App() {
  const [taskList, setTaskList] = useState(data);
  const [currentFilter, setCurrentFilter] = useState(FILTER_NAMES[0])
  
  const filtered = taskList.filter(FILTER_MAP[currentFilter])


  const taskItems = filtered.map((todo) => {
    ...
  });

  ...

  return (
    <div className="App">
      <Header />
      <AddTask addTask={addTask} />
      <div>
        <div className="task--list-btn">
          {/* IMPORTANT: FilterButton new API */}
          <FilterButton
            filterNames={FILTER_NAMES}
            onFilter={setCurrentFilter}
          />

          <div className="task--lst">
            <h2>TASKS</h2>
            <h3>{headingText}</h3>
            {taskItems}
          </div>
        </div>
        <div>No task Available</div>
      </div>
    </div>
  );
}

function FilterButton(props) {
  return (
    <div className="task--btns">
      {props.filterNames.map((filterName) => {
        return <button
          className={`${filterName}-tasks inputs`}
          onClick={() => props.onFilter(filterName)}
        >
          {filterName}
        </button>
      })}
    </div>
  )
}

Happy React journey! you are doing great.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 beqa