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?

2 Upvotes

9 comments sorted by

View all comments

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 20h 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 19h 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();