I'm 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.jsx:
import './App.css'
import Todo from './components/Todo';
import FilterButton from './components/FilterButton';
import Form from './components/form';
import { nanoid } from "nanoid";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const filterMap = {
All: () => true,
Active: (task) => !task.completed,
Completed: (task) => task.completed
};
const filterNames = Object.keys(filterMap);
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
const [filter, setFilter] = useState('ALL');
function toggleTaskCompleted(id) {
const updatedTasks = tasks.map((task) => {
// if this task has the same ID as the edited task
if (id === task.id) {
// use object spread to make a new object
// whose `completed` prop has been inverted
return {...task, completed: !task.completed}
}
return task;
});
setTasks(updatedTasks);
}
function deleteTask(id) {
const remainingTasks = tasks.filter((task) => id !== task.id);
setTasks(remainingTasks);
}
function editTask(id, newName) {
const editedTaskList = tasks.map((task) => {
// if this task has the same ID as the edited task
if (id === task.id) {
return {...task, name: newName}
}
return task;
});
setTasks(editedTaskList);
}
const taskList =tasks
.filter((filterNames[filter]))
.map((task)=> (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
deleteTask={deleteTask}
editTask={editTask}
/>
));
const filterList = filterNames.map((name) => (
<FilterButton
key={name}
name={name}
isPressed={name === filter}
setFilter={setFilter}
/>
));
function addTask(name) {
const newTask = { id: `todo-${nanoid()}`, name, completed: true };
setTasks([...tasks, newTask]);
}
const tasksNoun = taskList.length !== 1 ? 'tasks' : 'task';
const headingText = `${taskList.length} ${tasksNoun} remaining`;
const listHeadingRef = useRef(null);
const prevTaskLength = usePrevious(tasks.length);
useEffect(() => {
if (tasks.length - prevTaskLength === -1) {
listHeadingRef.current.focus();
}
}, [tasks.length, prevTaskLength]);
return (
<div className="todoapp stack-large">
<h1>TodoApp</h1>
<Form addTask={addTask} />
<div className="filters btn-group stack-exception">
{filterList}
</div>
<h2 id="list-heading" tabIndex="-1" ref={listHeadingRef}>
{headingText}
</h2>
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading"
>
{taskList}
</ul>
</div>
);
}
export default App;
FilterButton
''import React from "react";
function FilterButton(props) {
return (
<button
type="button"
className="btn toggle-btn"
aria-pressed={props.isPressed}
onClick={() => props.setFilter(props.name)}
>
<span className="visually-hidden">Show </span>
<span>{props.name}</span>
<span className="visually-hidden"> tasks</span>
</button>
);
}
export default FilterButton; ```
CodePudding user response:
You defined the filter state as:
const [filter, setFilter] = useState('ALL');
And you update the filter state inside FilterButton
.
But this just updates the state without changing the displayed list. You can add define a useEffect
that is triggered when the filter
state changes:
useEffect(() => {
setTasks(tasks.filter(filterMap[filter]));
}, [filter, tasks]);
CodePudding user response:
You're passing filterName
that actually only contains the keys, not the method.
const taskList = useMemo(()=>tasks
.filter(filterMap[filter])
.map((task)=> (
<Todo
.....
/>
)),[tasks,filter]);
Also just wrap your taskList with useMemo so whenever tasks & filter change your taskList will be updated.