0

Im building a calendar with a synced todo list. I am trying to save a specific date based on the user input to local storage. At the moment the todo is saved to local storage and is presented in a todo list with text and in the calendar but if I refresh the page the todo in the calendar vanishes.

In local storage it's written value:[object HTMLInputElement]

function saveTodosToLS() {
 const todosAsString = JSON.stringify(todos);
 localStorage.setItem("todos", todosAsString);
}

function loadTodos() {
 const todosAsString = localStorage.getItem('todos');
 todos = JSON.parse(todosAsString || '[]');
}

This is the part Im struggling with

function saveCalendarToLS() {
  const todoDateAsString = Date.parse('dateOfTodos');
  todoDateAsString = JSON.stringify(dateOfTodos);
  localStorage.setItem('dateOfTodos', todoDateAsString);
}

function loadTodosInLS() {
  const todoDateAsString = localStorage.getItem('dateOfTodos');
  dateOfTodos = Date.parse(todoDateAsString || '[]');
}

Dai
  • 141,631
  • 28
  • 261
  • 374
  • What do you mean by `Date.parse('dateOfTodos')` – Saeed Shamloo Dec 22 '21 at 14:38
  • @MirandaNilhag JSON **can** stringify numbers. In my experience the most reliable way to serialize JavaScript `Date` objects to JSON is by saving only the `Date`'s inner Unix timestamp (from `getTime()`) and then rehydrate using the `Date(number)` constructor. – Dai Dec 22 '21 at 14:47
  • You cannot pass a DOM element object (like a `HTMLInputElement`) into `localStorage`. You can only pass "POJO" objects (Plain Ol' JavaScript Objects). – Dai Dec 22 '21 at 14:53
  • I think you should update that part as so JSON.stringify(todoDateAsString); – bthn Dec 22 '21 at 14:54
  • An often useful thing to do while programming is to look at intermediate results when something goes wrong. In this case running `Date.parse('dateOfTodos');` in the browser console, and looking at the values of the local storage values after running the code from the question will likely be informative. – Ryan1729 Dec 22 '21 at 14:56
  • @Ryan1729 I get NaN when I do that – Miranda Nilhag Dec 22 '21 at 15:03
  • @Dai Can you give me an example? – Miranda Nilhag Dec 22 '21 at 15:04
  • @SaeedShamloo I use Date.parse because I read that JSON cannot stringify a number and to do so Date.parse should be used. I have only been writing js for a month so it's not easy for me to understand. – Miranda Nilhag Dec 22 '21 at 15:04
  • @MirandaNilhag See my posted answer below. – Dai Dec 22 '21 at 15:15

1 Answers1

0
  • You should not be using use globals (or any kind of shared-state, really) for passing data around. Programs are much easier to understand when data is passed in through function parameters and returned through return-values.
  • Practice defensive programming. It's downright essential in JavaScript because you don't have the benefit of a compiler to enforce type-safety.
    • What this means in practice is actually verifying that function parameter arguments are of the correct type (e.g. string, object, Date, etc) and their values are in expected ranges (e.g. disallow a Date value from 1000 years into the future).
  • Your Date.parse calls are completely incorrect (why are you passing a non-Date string literal?)

I would do it like this:

(Please see my bullet-list of notes below the code)

const LOCALSTORAGE_KEY_TODOS = "todos";

function saveTodos( todosArray ) {
    if( !Array.isArray( todosArray ) ) throw new Error( "Expected 'todosArray' to be an array but encountered " + ( typeof todosArray ) ); // Defensive programing!

    const todosAsJson = JSON.stringify( todosArray );

    window.localStorage.setItem( LOCALSTORAGE_KEY_TODOS, todosAsJson );
}

/** Returns an array of todo objects. Returns an empty array if nothing is saved in the store. */
function loadTodos() {
    
    const todosAsJson = window.localStorage.getItem( LOCALSTORAGE_KEY_TODOS );
    if( todosAsJson ) {
        try {
            const todos = JSON.parse( todosAsJson );
            if( Array.isArray( todos ) ) return todos;
        }
        catch( err ) {
            console.error( "Couldn't load TODOs: %o", err );
        }
    }

    return [];
}

function saveCalendar( date, todos ) {
    if( !isDate( date ) ) throw new Error( "Expected 'date' to be a Date object." );
    if( !Array.isArray( todosArray ) ) throw new Error( "Expected 'todosArray' to be an array but encountered " + ( typeof todosArray ) );
    
    const dateKey = 'CALENDAR_' + getDateOnlyString( date );

    const todosAsJson = JSON.stringify( todosArray );
    window.localStorage.setItem( dateKey, todosAsJson );
}

/** Returns an array of todo objects. Returns an empty array if nothing is saved in the store for that date. The time component of 'date' is ignored. */
function loadCalendar( date ) {

    if( !isDate( date ) ) throw new Error( "Expected 'date' to be a Date object." );

    const dateKey = 'CALENDAR_' + getDateOnlyString( date );

    //

    const todosAsJson = window.localStorage.getItem( dateKey );
    if( todosAsJson ) {
        try {
            const todos = JSON.parse( todosAsJson );
            if( Array.isArray( todos ) ) return todos;
        }
        catch( err ) {
            console.error( "Couldn't load TODOs: %o", err );
        }
    }

    return [];
}

/** Returns boolean true or false */
function isDate( value ) {
    // https://stackoverflow.com/questions/643782/how-to-check-whether-an-object-is-a-date

    return value instanceof Date && ( typeof value.getMonth === 'function' );
}

/** Returns `date` formatted as ISO 8601 yyyy-MM-dd */
function getDateOnlyString( date ) {
    // HACK: https://stackoverflow.com/questions/25159330/how-to-convert-an-iso-date-to-the-date-format-yyyy-mm-dd
    return date.toISOString().substring(0,10); // "if it works..."
}
  • Avoid inventing your own acronyms: I see you're using "LS" a shorthand for localStorage, however that is not a commonplace abbreviation in the JavaScript community, so I would avoid it and just call it what it is: localStorage.
  • You haven't posted a definition or specification of what a TODO object looks like, so my code below does not validate nor verify anything about the TODO objects except that they're actually object values.
  • Avoid magic literals, be they strings or numbers. When you need consistent key strings for collections (as with window.localStorage) you should use named constants (e.g. const LOCALSTORAGE_KEY_TODOS = 'todos';).
  • Remember that a Date object in JavaScript actually represents a date and a time-of-day value, so you cannot just use any old Date object to represent just a date. If you use a Date or its Unix timestamp value (from Date.getTime()) you'll find it impossible to retrieve existing items from localStorage because their keys will change every millisecond instead of once-per-day.
    • My code below works-around that by using toISOString() to get a full ISO 8601 date+time string (of the form yyyy-MM-dd HH:mm:sszzz) and uses .substring(0,10) as a cheap-trick to trim-off the time-of-day components so that the returned string only contains date information (this is in function getDateOnlyString).
  • There is a fair bit of repetitiveness in my code below (e.g. the loadTodos and loadCalendar functions are almost identical, spare for the localStorage key value they use, so that could be rewritten without the duplication of logic. That's an exercise left up for the reader.
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Thanks Dai! I tried it but got this "Uncaught ReferenceError: KEY_TODOS is not defined". – Miranda Nilhag Dec 22 '21 at 15:38
  • @MirandaNilhag That was a typo. I've fixed it now, but I had hoped you would have been able to solve that yourself. – Dai Dec 22 '21 at 17:22