I am relatively new to React and I am trying to create a todo list that would display current and completed tasks simultaneously using LocalStorage.
My tasks contain a property called "active", a boolean set to true when a user adds a new task. Clicking on the checkbox for a task toggles "active". I have a completedTasks array that filters out any items with "active" set to false.
The problem that I’m running into is my lists getting completely overwritten when I toggle a task.
I’m setting inputted tasks into localstorage and filtering that data in my tasks arrays.
// Task list state
const [tasks, setTasks] = useState([]);
const [completedTasks, setCompletedTasks] = useState([]);
// READ - useEffect to display data
// If found any tasks in local storage then set them to tasks states
useEffect(() => {
const storedTasks = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (storedTasks) {
setTasks(storedTasks.filter((task) => task.active === true));
setCompletedTasks(storedTasks.filter((task) => task.active === false));
}
}, [setTasks, setCompletedTasks]);
// CREATE - function to save new tasks when user fills out form
const addTask = (newTask) => {
setTasks(newTask);
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newTask));
};
I have a toggle handler for the checkbox input in my List component. The handler returns a new list with updated data only for the "active" property. This is the point where I’m having trouble on how to separate the data.
const toggleCheckbox = (id) => {
const toggledTasks = props.tasks.map((task) => {
if (id === task.id) {
return { ...task, active: !task.active };
}
return task;
});
// toggledTasks.map((task) => {
// if (task.active === false) {
// props.setCompletedTasks([...props.tasks, task]);
// } else {
// props.setTasks([...props.tasks, task]);
// }
// return task;
// });
props.setToggledTasks(toggledTasks);
};
I want any task I toggle to appear in the other list individually (if a task is checked then immediately appear on the Completed list and vice vera). And the program does do that, only with it deleting the previous tasks that were on that list…
const addToggledTask = (toggledTasks) => {
// console.log("toggledTasks");
// console.log(toggledTasks);
// console.log("filtering active from toggled Tasks");
// console.log(
// toggledTasks.filter((activeTask) => activeTask.active === true)
// );
// console.log("filtering false from toggled Tasks");
// console.log(
// toggledTasks.filter((completedTask) => completedTask.active === false)
// );
setTasks(toggledTasks.filter((task) => task.active === true));
setCompletedTasks(toggledTasks.filter((task) => task.active === false));
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toggledTasks));
// console.log("tasks after setTasks and adding to localstorage");
// console.log(tasks);
// console.log(completedTasks);
};
I’ve left in some of my commented out attempts to give an idea where I was trying to go with this.
Any advice would be much appreciated because I’ve been stumped for a while and I feel like there is an easy solution that I’m not yet understanding.
2
Answers
More information is needed for better understanding of your code and what you are trying to achieve. The first advice I would give is to set
toggledTasks
to localstorage inside of auseEffect
hook.If you ingest the tasks/todos from localStorage and split them into separate states based on the
active
property, then anytime you toggle a task/todo active/inactive you’d need to enqueue two state updates: one to remove it from one state array and a second to add the updated task/todo to the other state array.This is unnecessary. You can store all the tasks/todos in a single state and compute the derived "active" and "inactive" "states" for rendering. This way you have only a single state to update and maintain.
You should also use an initialization function to set the initial state from localStorage instead of using a
useEffect
hook to update it at the end of the initial render.Here’s an example using your code with the recommended suggestions: