skip to Main Content

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


  1. 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 a useEffect hook.

    useEffect(() => {
       localStorage.setItem(LOCAL_STORAGE_KEY, 
          JSON.stringify([...tasks, ...completedTasks]));
    }, [tasks, completedTasks])
    
    Login or Signup to reply.
  2. 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:

    const Task = ({ task, toggleHandler }) => (
      <div>
        <label>
          {task.message}
          <input
            type="checkbox"
            checked={task.active}
            onChange={() => toggleHandler(task.id)}
          />
        </label>
      </div>
    );
    
    import "./styles.css";
    import { useEffect, useState, useMemo } from "react";
    import { nanoid } from "nanoid";
    
    export default function App() {
      // Initialize tasks from localStorage
      const [tasks, setTasks] = useState(() => {
        return JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) ?? [];
      });
    
      // Persist state changes to localStorage
      useEffect(() => {
        localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(tasks));
      }, [tasks]);
    
      const addTask = (e) => {
        e.preventDefault();
        const newTask = e.target.newTask.value;
        setTasks((tasks) =>
          tasks.concat({
            message: newTask,
            active: true,
            id: nanoid()
          })
        );
        e.target.reset();
      };
    
      // Compute (and memoize) derived active/inactive task arrays
      const activeTasks = useMemo(() => tasks.filter((task) => task.active), [
        tasks
      ]);
      const inactiveTasks = useMemo(() => tasks.filter((task) => !task.active), [
        tasks
      ]);
    
      const toggleCheckbox = (id) =>
        setTasks((tasks) =>
          tasks.map((task) =>
            task.id === id ? { ...task, active: !task.active } : task
          )
        );
    
      return (
        <div className="App">
          <form onSubmit={addTask}>
            <label>
              Add task
              <input name="newTask" type="text" />
            </label>
            <button type="submit">Add</button>
            <button type="reset">Clear</button>
          </form>
    
          <div className="container">
            <div className="active">
              <h2>Active</h2>
              {activeTasks.map((task) => (
                <Task key={task.id} task={task} toggleHandler={toggleCheckbox} />
              ))}
            </div>
            <div className="inactive">
              <h2>Completed</h2>
              {inactiveTasks.map((task) => (
                <Task key={task.id} task={task} toggleHandler={toggleCheckbox} />
              ))}
            </div>
          </div>
        </div>
      );
    }
    

    Edit having-trouble-showing-filtered-lists-using-state-in-react

    enter image description here

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