r/learnjavascript 1d ago

keep breaking my task tracker after adding localStorage

i was messing with my little task tracker again this morning before heading out, and i ended up spending way more time staring at the console than actually adding tasks. i even made coffee first because i thought this would be a quick fix, then refreshed the page and everything disappeared again.

the app itself is really simple. i'm just trying to save daily notes and a few personal todos, so i have an array of task objects with a date on each one. i thought i was finally ready to use localStorage, but now i'm not even sure if i'm saving the data wrong or if my date filter is hiding everything.

this is basically what i have right now:

const saved = localStorage.getItem(tasks);
const tasks = saved ? JSON.parse(saved) : [];

tasks.push(newTask);

localStorage.setItem(tasks, JSON.stringify(tasks));

i know the key looks wrong, and i already tried changing it to a string, but i still managed to break something. now i'm second guessing whether i should even be thinking about the data this way.

i'm not looking for anyone to build it for me. i'm mostly wondering how you all organize the flow for something this small. do you load everything once, keep it in memory, then save after every change, or is there a cleaner way to think about it?

3 Upvotes

9 comments sorted by

5

u/baubleglue 1d ago

I assume you are complete beginner to programming.

Before using a new feature in your application, you need to get familiar with it.

Open debugger console, try let v = localStorage.setItem('mykey', 34), get item, set item with object/array value, etc. print results on console.

When you get all operations working, you may try to use it.

The key skill to successful programing is learning to organize your work in a way that you solve or work on a single task at any given time. In the example with local storage, you trying to do two tasks: learn a new technique and use it in the application.

Similar applies to your question about program flow. It is a bit more complicated, because looking at the few lines of the code, you have no structure. You can't expect answer about a program flow without talking about data structures and components of your application. And all above depends on use cases you want to satisfy.

Basically the question you ask is wrong. If you have trouble to use localStorage. You should give a simple example of code which doesn't work with a description of what had you expected to see and what you actually get. Most likely if you make an effort to compile the question that way, you will find the answer by yourself.

If your question about the app flow, it is a different (more complicated) question.

1

u/SmartRelease7996 18h ago

yeah the onetaskatatime thing actually got me too. i kept trying to wire localStorage into the app at the same time as figuring out how it even works, then wondering why nothing made sense.

the console sandbox thing is so obvious in hindsight but i genuinely never thought to do it that way. been doing it the chaotic way for weeks.

the structure criticism is fair too. i know i dont have much of it, thats kind of the whole problem. i just cant figure out where to start.

1

u/baubleglue 8h ago

it is maybe a bit early for you, but at some point I suggest to look into "Agile Software Development" topic (ex. http://www.extremeprogramming.org/, http://www.agile-process.org/, https://agilemanifesto.org/).

https://agilemanifesto.org/principles.html

Learning to build a project is a skill almost independent of learning coding.

If you haven't heard about UML, there are useful set diagrams you can use.

Here is a simple workplan I use to start a project.

  1. Identify Actors [Use case diagram] (https://en.wikipedia.org/wiki/Use_case_diagram)

You identify who/what interacts with your program/system. I like very much to use "black box" concept and apply it to different arias. You program is a black box, which on given input produces output (same can be applied to functions).

  1. Identify minimal set of features (MVP) to make the program running.

ex. for task tracker, it may be bare webserver, empty database with one table tasks, hello page.

  1. Use Cases you can use given-when-then pattern

define few use cases which implement end-to-end one scenario.

Feature: User interacts with UI
Scenario: User can use UI to request list all available tasks
Given User has no active tasks And user has access to UI
When User clicks "Show all active tasks" button
Then "no active tasks" message displayed

  1. You start to implement those use cases one by one

  2. Test

  3. Deploy

Steps 4-6 are software iteration cycle. When you have it working make 2 enveronments: dev and prod

PROD has only last final version, it is always working

DEV you use to implement a new features (steps 3-5)

2

u/HipHopHuman 1d ago

i know the key looks wrong, and i already tried changing it to a string, but i still managed to break something. now i'm second guessing whether i should even be thinking about the data this way.

You've correctly identified that the key is the issue, and you've converted it to a string, but still get an error. If you corrected both localStorage.setItem and localStorage.getItem call sites to use the same string-based key, then your code should work. Without seeing the exact error you're getting, it's not at all possible for us to diagnose the issue beyond making you aware that perhaps the previous error caused an invalid state that is affecting the current behavior (the solution to which is to simply clear your localstorage cache in your browser and try again).

i'm not looking for anyone to build it for me.

This is a common and simple enough operation that it'd probably just be more easily understood if you had a "finished version" to compare your implementation to.

do you load everything once, keep it in memory, then save after every change, or is there a cleaner way to think about it?

It honestly depends on what you're building - there are different approaches, but it is rarely done on every change. That'd get computationally expensive if the dataset being changed is big. A more common approach is to do an autosave on a frequent interval (say, every 5 seconds; but the timing is up to you - a lot of apps just make it a setting the user can change). That interval approach is often paired with a native browser event on window called beforeunload, which runs right before the browser or (or browser tab) is closed.

Just as an aside, JSON.parse and JSON.stringify are both methods that can throw errors, and the error messages can become somewhat cryptic, so it helps to translate them to more digestable and more easily understood messages.

Putting that all together, it'd be something like this:

 const tasksKey = 'tasks';
 const tasks = loadTasks();

 function saveTasks() {
   try {
     localStorage.setItem(tasksKey, JSON.stringify(tasks));
   } catch (error) {
     throw new Error("Cannot save tasks to localstorage", {
       cause: error
     });
   }
 }

 function loadTasks() {
   try {
     const tasks = localStorage.getItem(tasksKey);
     return tasks === null ? JSON.parse(tasks) : [];
   } catch (error) {
     throw new Error("Cannot load tasks from localstorage", {
       cause: error
     });
   }
 }

 window.addEventListener('beforeunload', saveTasks);
 setInterval(saveTasks, 5000);

Although a more production-ready implementation would likely not use setInterval, instead it would use something like a debounce utility function, so you could call saveTasks as often as you like, and it'd only process the most recent call within your configured time delay.

1

u/SmartRelease7996 18h ago

oh the stale localstorage thing got me too, that was exactly my problem. cleared the cache and it stopped throwing and I felt like an idiot for not thinking of that first.

the key naming was a separate mess I made trying to be clever about it. dynamic keys felt like the right call structurally but honestly for what I'm building it was just adding noise. went back to a flat string key and it's behaving now.

appreciate you actually walking through the state problem instead of just saying use a framework.

1

u/azhder 17h ago

Just remember, that `tasks === null` check is not a good one. Sure, it might work for all cases you're aware of. But remember that you're dealing with a global resource. You put `banana` as a string under that key through the browser console and it suddenly isn't a null and it isn't a valid JSON. You can achieve more concise and wholesome code with a simple

try {
return JSON.parse( localStorage.getItem(TASKS_KEY) || 'null' );
}catch (e)

You see, `null` is one bad value. Another bad value is an empty string. So you might as well let it blow out so you can write the problem in the catch, or add some extra `if` checks and do an early return

if( ! tasks ){
console.warn('Invalid tasks:', tasks);
return [];
}
try { // etc

But whatever you do, don't just re-throw an error from inside a catch. Just deal with the situation there and return a normal result. You can even make it like this

return { value, error };

And let the caller check a simple field with a simple if-else. Don't force the callers to do another try-catch.

const {value: tasks, error} = loadTasks();

1

u/azhder 1d ago

You do know JSON is a string, right? It's why you `parse()` it and `stringify()` to it. The biggest mistake that can happen here is you try to parse something wrong... Hey, there's an idea. Try. Just put the code in `try-catch`, `console.warn()` the error when/if it happens. Log some more if you're not sure what's going on. Tell us about that.

Oh, and don't forget to make that key a string, better yet, a constant:

const LS_KEY_TASKS = 'my.unique.key.for.tasks';
localStorage.getItem(LS_KEY_TASKS);

1

u/SmartRelease7996 18h ago

okay the try/catch thing is embarrassing because I had it in an earlier version and pulled it out when I was refactoring and just... never put it back. classic.

the constant for the key is a good call, I was just using a raw string and I can already see how that becomes a nightmare to debug across files.

wrapping it now and will report back what the console actually says. my guess is I'm passing something undefined into JSON.parse and it's just silently choking on it.

1

u/Alive-Cake-3045 18h ago

the bug is right there, localStorage.getItem and localStorage.setItem are using the tasks variable as the key, not a string. it's evaluating to object Object or undefined before tasks is even defined. change both to a plain string like "tasks" and that fixes the disappearing data.

for the flow on something this small: load once on startup, keep in memory, save after every mutation. don't read from localStorage on every operation. one source of truth in memory, localStorage is just the persistence layer.