0

I'm very new to react so please bare with me. I am creating a simple toDo list react application using localstorage. I have InputTask component that keeps track of the input box state (where the user enters the task they want to add).

My goal right now is to add the task the user enters to localstorage then using another state called tasks update all tasks to contain the new task the user entered. I feel like i'm overcomplicating this please help.

import React, { useEffect, useState } from "react";

// Imported Components below
import CurrentTasks from "./CurrentTasks";

function InputTask() {
  // Initial array for storing list items
  // Tracking state of input box
  const [task, setTask] = useState();
  const [tasks, setTasks] = useState([]);

  function handleAddTask() {
    // Create array to add to local storage of tasks
    let allTasks = [];
    // push new task to array
    allTasks.push(task);
    // add task to localstorage
    localStorage.setItem("tasks", JSON.stringify(allTasks));
    // update setTasks with new task (This contains all tasks prev)
    setTasks(JSON.parse(localStorage.getItem("tasks")));
  }

  return (
    <div className="container">
      <div className="input-group mt-4">
        <input
          onChange={e => setTask(e.target.value)}
          type="text"
          className="form-control form-control-lg"
          placeholder="Write task here..."
        />
        <div className="input-group-append">
          <button onClick={handleAddTask} className="btn btn-outline-secondary">
            Add
          </button>
        </div>
      </div>
      <CurrentTasks />
    </div>
  );
}

export default InputTask;

The issue I am running into is that the tasks state is always behind. So if i were to add "Make bed" to the task as the first item "Make bed" would not appear until I enter in a new task such as "fold cloths". Then, "fold cloths" will not appear until I enter in a new task... ect..

enter image description here

Nick
  • 624
  • 8
  • 24
  • 1
    The weird thing about LocalStorage is that even though it's a synchronous JavaScript function, the actual writing to disk portion (a native function) that the browser handles may be asynchronous, and doesn't guarantee that it will be done when the JS side returns. ALSO every time you call `localStorage.setItem()`, you're clobbering what you had in there already, since you are only serializing the current task. There's really no need to get it back out of localStorage though, since you already have its value! – tobiasfried Oct 16 '20 at 20:44
  • Agreed, but to hammer the point home more, local storage should be used only to initialize state and persist data cached in memory (i.e. your component state). Read and initialize app state *from* storage on mount, update in memory during normal lifecycle, periodically persist back to storage. – Drew Reese Oct 16 '20 at 20:50
  • Absolutely. My answer below gives a very basic illustration, but @Nick you will have to decide when to persist. – tobiasfried Oct 16 '20 at 21:05

3 Answers3

2

Per my comment above, localStorage doesn't guarantee that it's done writing to disk before continuing JS execution. But that's okay, since what you really want to do is get the persisted tasks ONCE when the component renders, and only persist the new set of tasks very rarely (on unmount, once a minute, etc.).

The best way to structure this code would probably be:

  1. On first render, get the stored tasks from localStorage and put it in a variable
  2. On submit, simply append your new task to that variable
  3. At an appropriate time (unmount, or periodically), persist the whole thing to localStorage again.
function InputTask() {
  const [task, setTask] = useState();
  // Get the persisted tasks ONLY on first render
  const [tasks, setTasks] = useState(JSON.parse(localStorage.getItem("tasks") || "[]"));

  function handleAddTask() {
    // Just set tasks to tasks + task
    setTasks([...tasks, task]);
    // And probably clear the input
    setTask(""); 
  }

  // Serialize all tasks on unmount
  // (Just for illustration, you may want to do it more often than this)
  useEffect(() => {
    return () => localStorage.setItem("tasks", JSON.stringify(tasks));
  }, []);
  ...
}
tobiasfried
  • 1,689
  • 8
  • 19
  • 2
    I was getting very confused as to why my `tasks` state was always one step behind the state it should be. Before I would have `console.log(tasks)` in the `handleAddTask()` function to display the updated state (once I added a task). It was frustrating because the state that was shown in the log was always one state behind. Then I moved the `console.log(tasks)` into the `useEffect()` function and it was display the updated state correctly. I believe this is because the `useEffect()` function was rerendering causing the state to be updated? – Nick Oct 17 '20 at 01:25
1

You should update the state directly insted of a seperate array.

Push method in React Hooks (useState)?

Then you should use onChange to update localstorage

https://www.robinwieruch.de/local-storage-react

RustyJames
  • 123
  • 7
-1

tasks length will always be one in your case. Isn't it? I guess handleAddTask() should be like this.

function handleAddTask() {
    // Create array to add to local storage of tasks
    let allTasks = JSON.parse(localStorage.getItem("tasks"));
    allTasks.push(task);
    localStorage.setItem("tasks", JSON.stringify(allTasks));
    setTasks(allTasks);
  }
Nrz
  • 1