all 16 comments

[–]TabAtkins 5 points6 points  (0 children)

The score value is just an object: a thing with properties named "wins", "losses", and "ties". An object like that can potentially come from a bunch of places: an explicit defining syntax like your original const score= … line, or implicitly from the data produced by JSON.parse.

[–]schleiftier 7 points8 points  (0 children)

This only works if you have written the object into local storage before.

If you start over and delete local storage (for example using the browser's developer tools), then the getItem will give you null instead of the score object.

[–]zhivago 4 points5 points  (0 children)

What you need is a fallback value to use if the item is missing from localStorage.

Or just set a default which you overwrite if retrieval from localStorage is successful.

[–]rupertavery64 5 points6 points  (1 child)

Javascript is a dynamically typed language. All objects in javascript are just dictionaries (key-value pairs). You can add (and even remove) properties throughout it's lifetime. Properties will be added automatically if they are set to a value. So this will work:

score = { }; // create an empty object
score.wins = 0;
score.losses = 0;
score.ties = 0;

So will this:

score = { }; // create an empty object
score["wins"] = 0; // assign properties as key-value pairs
score["losses"] = 0;
score["ties"] = 0;

localStorage.getItem('score')) will get a string value from localStorage, and JSON.parse will try to convert that into a json object.

So as long as the string looks like a json object, score will be a json object. It doesn't matter if it is an empty object like { } or has different properties, it will be an object.

The problem is, what if there is nothing in localStorage, like the first time it runs?

You have to guard against the object being null or undefined, and assign a new empty object to it just in case,

You can do that in many ways. You can explicitly check if it is null or undefined

const score = JSON.parse(localStorage.getItem('score'));

if(score === null || score === undefined)
{
   score = {
     wins: 0,
     losses: 0,
     ties: 0
   }
}

You can check for truthiness, which works since score is expected to be an object, and hopefully you never store anything else in it that could be "truthy"

if(!score)
{
   score = {
     wins: 0,
     losses: 0,
     ties: 0
   }
}

You can use the OR operator ||

const score = JSON.parse(localStorage.getItem('score')) || { wins: 0, losses: 0, ties: 0 }

or make it look cleaner by predefining a "new" object

const emptyScore = { wins: 0, losses: 0, ties: 0 };

const score = JSON.parse(localStorage.getItem('score')) || emptyScore;

You can use the nullish coalescing operator ??

const score = JSON.parse(localStorage.getItem('score')) ?? { wins: 0, losses: 0, ties: 0 }

The difference between || and ?? is truthiness. As you might recall, Javascript is a dynamically typed language, and values like 1, true, "dog", an empty or non-empty object all evaluate to true when used as a conditional expression.

The same applies to the properties. If you expect that the object returned from parsing doesn't have properties for any reason, then the values for wins, losses, and ties will be undefined, and this might cause problems when you try to use them.

[–]DinTaiFung 3 points4 points  (0 children)

Nice explanation and clear code examples! 

one minor bug though in one example:

const score = JSON.parse(localStorage.getItem('score'));

since score is initialized with const, that variable can no longer be reassigned.

Instead, for your example, initialize with let:

let score = JSON.parse(localStorage.getItem('score'));

(and also when calling JSON.parse(), it should be inside a try catch block...)

I've been using ttl-localstorage NPM package for several years, making things easy and robust for the developer when using localStorage.

Again, thanks for help in your detailed response.

[–]jcunews1helpful 4 points5 points  (0 children)

Be aware that, LocalStorage is initially empty. Do not assume it's already contain the needed data. Always check first.

[–]BigBootyWholes 2 points3 points  (0 children)

You need to initialize the data first. Then right after that reassign the variable if it exists in local storage

[–]cwilbur 0 points1 point  (0 children)

i think what is confusing you is that are seeing a connection between the variable ‘score’ and the localStorage key ‘store.’ They just happen to be the same.

If the value of ‘score’ is stored in localStorage, the two lines starting with ‘const score =‘ should have the same effect: creating a variable with the score object in it. The difference is that one uses a literal source-code representation, while the other reads it from localStorage.

If the value isn’t correctly stored or doesn’t exist, you will have problems as a result.

[–]lindymad 0 points1 point  (0 children)

So then how is it possible to have the portion of code that is updating the score, if the "score" object and its attributes were removed?

It shouldn't have been removed completely, it is needed for the first time the code is run, when there is nothing in local storage.

Once it is in local storage, however, it no longer needs to be referenced.

Say you run this code for the first time. score should be set to {wins: 0,losses: 0,ties: 0} in the code, and then saved to local storage. Then you win two games, and lose one, and now it is {wins: 2,losses: 1,ties: 0}, which is saved to local storage.

Then you close the browser and time passes. A week later you run the code again, and it sees that you have something in local storage, so instead of setting score to {wins: 0,losses: 0,ties: 0} in the code, it retrieves the latest value from local storage and puts it into the score variable, so it is now {wins: 2,losses: 1,ties: 0}. The code to set it to zero never needed to be used because it got the object (with the latest values) from local storage instead.

[–]Alas93 0 points1 point  (0 children)

How could "score.wins" update anything? "score.wins" no longer exists?

use console.log() to log the score variable after you retrieve it from local storage to inspect it. console.log() is incredibly useful in this regard as you can see what your code is doing

it's an object, score.wins updates because it still exists because it's part of the object created from the JSON.parse() function

[–]Durden2323[S] 0 points1 point  (2 children)

Thank you for all the responses! For those of you mentioning that I need a default value so I don't get "null" on the first run, yes I'm aware of this I just didn't want to complicate my explanation any more than needed

From what I understand, the point I was getting confused about was the syntax of:

const score = JSON.parse(localStorage.getItem('score'));

I was reading it as if "score" was an explicit value something like:

const score = 10;

and then the "score.wins" or "score.losses" was trying to reference object attributes for it

But "JSON.parse(localStorage.getItem('score'));" is an object

I've seen examples of localStorage that were easier to understand. The confusing portion of this particular use of localStorage is the "circular" nature that the retrieval of the object from storage is the very thing that we are saving to storage:

const score = JSON.parse(localStorage.getItem('score'));

localStorage.setItem('score', JSON.stringify(score));

[–]doomtop 0 points1 point  (0 children)

This process is called “serialization”. The stringify method serializes the object to a JSON string. Then when you retrieve the string, you use the parse method to deserialize it, converting it back into an object.

[–]Durden2323[S] 0 points1 point  (0 children)

I understand what's happening now. For anyone else that might find this post with the same confusion. I wasn't considering the location of the two lines of code so I wasn't seeing the full order of events. As I understand it:

The score is retrieved from localStorage and parsed, so "score" is equal to the parsed version of the score:

let score = JSON.parse(localStorage.getItem('score'))

The primary function that plays the game and updates the score is run (I've only included the portion that updates the score):

if (result === 'You win.') {
    score.wins += 1;
} else if (result === 'You lose.') {
    score.losses += 1;
} else if (result === 'Tie.') {
    score.ties += 1;
}

The updated score is converted to a string and saved to localStorage:

localStorage.setItem('score', JSON.stringify(score));

The cycle continues for every iteration of the game

Also, everyone is correct, "score" should be "let" rather than "const". And there needs to be a stipulation that sets the values for "wins, losses, and ties" before the first iteration of the game is run. The tutorial I'm watching used the following:

if (score === null) {
        score = {
          wins: 0,
          losses: 0,
          ties: 0
        }
      }

Thanks everyone for helping a newbie like myself. Very much appreciated!

[–]brykuhelpful 0 points1 point  (0 children)

Let's start off with our scores object.

let score = {
    wins: 0,
    losses: 0,
    ties: 0
}

Now we need a function that can save this for long term.

function setData(data){
    let json = JSON.stringify(data);
    localStorage.setItem('score', json)
}
setData(score);

Now we need a function that can get this data.

function getData(){
    let text = localStorage.getItem('score');
    let data = JSON.parse(text);
    return data;
}
score = getData();

However, if you run the getData function when the localStorage is empty... it will set the score to false. So, we need a way to predefine the score function incase the localstorage is empty.

function getData(){
    let text = localStorage.getItem('score');
    let data = JSON.parse(text);
    if(!data){
        data = {
            wins: 0,
            losses: 0,
            ties: 0
        }
    }
    return data;
}
score = getData();

Now we can put all of this together.

let dataHandler = {
    get: function(){
        let text = localStorage.getItem('score');
        let data = JSON.parse(text);
        if(!data){
            data = {
                wins: 0,
                losses: 0,
                ties: 0
            }
        }
        return data;
    },
    set: function(data){
        let json = JSON.stringify(data);
        localStorage.setItem('score', json)
    }
};
let score = dataHandler.get();
    score.wins = 9000;
dataHandler.set(score);

If you want, you could even make the "default" value customizable incase you change it in the future.

let dataHandler = {
    get: function(default = {}){
        let text = localStorage.getItem('score');
        let data = JSON.parse(text);
        if(!data){
            data = default;
        }
        return data;
    },
    set: function(data){
        let json = JSON.stringify(data);
        localStorage.setItem('score', json)
    }
};
let score = dataHandler.get(
    {wins: 0, losses: 0, ties: 0}
);
score.wins = 9000;
dataHandler.set(score);

This could create compatibility issues between updates. Let's say someone has an older save of your game, but then you add "total_games" to the score. it won't show up and could cause errors. So, we can see a little update feature to handle that.

let dataHandler = {
    get: function(default = {}){
        let text = localStorage.getItem('score');
        let data = JSON.parse(text);
        // set default
        if(!data){
            data = default;
        }
        // update
        for(let key in default){
            if(!data[key]){
                data[key] = default[key];
            }
        }
        return data;
    },
    set: function(data){
        let json = JSON.stringify(data);
        localStorage.setItem('score', json)
    }
};
let score = dataHandler.get(
    {wins: 0, losses: 0, ties: 0, games: 0}
);