discord info command cannot read property x of undefined

I have been trying, for 6 hours, to make a command that you put in a number of pokémon you released during your shiny hunts (To give an egg count), and the discord bot stores that information in a json file. The command works like this:

/shiny (The pokémon which you released) (The amount of pokémon you released)

But, no matter what I change, I always come back to the same result, Cannot read property 'x' of undefined! So here’s my current code:

client.on('message', message => {

    const path = require('path');
    const fs = require('fs');

    const args = message.content.slice(prefix.length).trim().split(/ +/);
    const command = args.shift().toLowerCase();

    const Pokémon = args[0];
    const amount = args[1];

    const shiniesDataPath = path.resolve(__dirname, './Storage/shiniesData.json');

    function loadShiniesData() {
        return JSON.parse(fs.readFileSync(shiniesDataPath).toString());
    };

    function saveShiniesData(shiniesData) {
        fs.writeFileSync(shiniesDataPath, JSON.stringify(shiniesData, null, 4))
    };

    const shiniesData = loadShiniesData();

    if (Pokémon && !amount && command === 'shiny') {
        return message.channel.send('You did not send the released pokémon amount.')
    };

    const trainer = shiniesData[message.author.id];

    if (!trainer) {
        shiniesData[message.author.id] = {};
    }

    const pokémon = trainer[Pokémon];

    if (!pokémon) shiniesData[message.author.id][Pokémon].eggs = 0

    let numberOfEggs = pokémon.eggs

    if (Pokémon && amount && command === 'shiny') {
        numberOfEggs += amount
        saveShiniesData[shiniesData]

        message.channel.send('The storage has been updated');
    }

    if (message.content === '/shinies')
        message.channel.send('I haven't figured this one out, but this isn't the problem');

});

Please help, I have no idea what to do next.

Answer

I’m going to explain the problem(s) and the solution in detail, to ensure that you understand why/where you are getting this error.

The Problem

The error is Cannot read property 'x' of undefined, which indicates that something is going wrong when you are trying to get a value from an object. Essentially, somewhere in this code you are doing the equivalent of someObject["x"] but someObject ended up being undefined, though obviously someObject and "x" would be substituted with some of the variables in your code. This means that there is some object someObject that is undefined, and some key value that is equal to “x”.

That slightly narrows down what the cause of the problem could be to these lines:

  • const trainer = shiniesData[message.author.id];
  • const pokémon = trainer[Pokémon];
  • if (!pokémon) shiniesData[message.author.id][Pokémon].eggs = 0

We can further narrow down that list of problematic lines given that the property is equal to “x”, and message.author.id cannot be equal to “x”:

  1. const pokémon = trainer[Pokémon];
  2. if (!pokémon) shiniesData[message.author.id][Pokémon].eggs = 0

Now, I can see several clear problems with those two lines of code. First of all, think about whether or not it is possible that trainer is undefined. What are you doing if shiniesData doesn’t contain the user’s ID yet? You’re doing this:

const trainer = shiniesData[message.author.id];

if (!trainer) {
    shiniesData[message.author.id] = {};
}

In that code, if trainer is undefined, you are simply setting shiniesData[message.author.id] to an empty object, but trainer remains undefined. Now when you go to the next line, the first line on our list of problematic lines, you’re doing:

const pokémon = trainer[Pokémon];

But if the user’s ID was not already in shiniesData, even though you’ve added the ID to shiniesData, trainer remains undefined. So now you’re trying to do undefined[Pokemon]. And that’s where the error you’re getting comes in, because you are trying to read a property of undefined.

However, there is actually a second error in your code which you have not seen yet since the current error you’re getting prevents you from getting that far in the code. It has to do with the second problematic line on our list:

if (!pokémon) shiniesData[message.author.id][Pokémon].eggs = 0

So we have already set shiniesData[message.author.id] to {} when trainer is undefined. Though {} is not the same as undefined, an empty object doesn’t have the Pokemon property either, so doing shiniesData[message.author.id][Pokémon].eggs = 0 will not work because this will be the equivalent of doing undefined.eggs = 0. You would get the error: Cannot set property 'eggs' of undefined.

The Solution

So, how do we solve those two problems? They’re closely related, simple fixes. Here’s how it would look in your code, with comments to explain the fixes:

client.on('message', message => {

    const path = require('path');
    const fs = require('fs');

    const args = message.content.slice(prefix.length).trim().split(/ +/);
    const command = args.shift().toLowerCase();

    const Pokémon = args[0];
    const amount = args[1];

    const shiniesDataPath = path.resolve(__dirname, './Storage/shiniesData.json');

    function loadShiniesData() {
        return JSON.parse(fs.readFileSync(shiniesDataPath).toString());
    };

    function saveShiniesData(shiniesData) {
        fs.writeFileSync(shiniesDataPath, JSON.stringify(shiniesData, null, 4))
    };

    const shiniesData = loadShiniesData();

    if (Pokémon && !amount && command === 'shiny') {
        return message.channel.send('You did not send the released pokémon amount.')
    };

    var trainer = shiniesData[message.author.id];

    //Location of Problem #1
    if (!trainer) {

        //Set trainer to an empty object (solves Problem #1)
        trainer = {};

        //Then set the Pokémon property of trainer to its own object 
        //And set the value of 'eggs' to 0 (solves Problem #2)
        trainer[Pokémon] = {eggs: 0};

        //Then set this to 'trainer' since their values should match
        shiniesData[message.author.id] = trainer;
    }

    //Location of Problem #2 (no longer necessary due to above fix)
    //if (!pokémon || Object.keys(pokémon).length == 0) shiniesData[message.author.id][Pokémon].eggs = 0

    if (Pokémon && amount && command === 'shiny') {
        //Increment number of eggs by amount
        trainer[Pokémon].eggs += Number(amount);

        //Then set this to 'trainer' since their values should match
        shiniesData[message.author.id] = trainer;

        //An additional issue, 'saveShiniesData' is a method, not an object:
        //saveShiniesData[shiniesData] <- is wrong
        saveShiniesData(shiniesData); // <- is right

        message.channel.send('The storage has been updated');
    }

    if (message.content === '/shinies')
        message.channel.send('I haven't figured this one out, but this isn't the problem');

});

As you can see, the simple solution to your problem was to properly handle instances where the user’s ID was not already present in the shiniesData object.