Javascript 1st Portfolio Project - Unknown Typeerror issues on nested array

(It’s not really the build-a-library project but there was no selector for the Javascript Syntax Portfolio Project)

So I’ve been running into issues with Javascript and could really use the help. I feel like I must be missing a piece of the puzzle bc I am pretty lost ngl.

Essentially, this code aims to randomly select/give a joke from 5 arrays. I have individual getters for the arrays that are working just fine and successfully randomly pick/give a random joke from their respective arrays.

A different array, _jokes[], nests the other 5 within it. randomJoke() is a method that accesses this array and attempts to return a single joke from one of the 5 nested arrays. But I am ending up with a lot of confusing typeerrors. And idk why it’s not working. I’ve tried the following code:

randomJoke() {
        let categoryIndex = Math.floor(Math.random() * this._jokes.length);
        let category = this._jokes[categoryIndex];
        let jokeIndex = Math.floor(Math.random() * category.length);
        return category[jokeIndex];
    }

I’ve also tried:

randomJoke() {
        let i = Math.floor(Math.random() * this._jokes.length);
        let j = Math.floor(Math.random() * this._jokes[i].length);
        return this._jokes[i][j];
    }

Unfortunately, both return typeerrors. Here’s the full code:

const objJokes = {
    _edgyJokes: ['I told my psychiatrist I had suicidal thoughts. He said from now on, I have to pay in advance.',
    'Why did the hipster burn his tongue? Because he drank his coffee before it was cool.',
    'Why did the scarecrow win an award? Because he was outstanding in his field, unlike the rest of us losers.',
    'I tried to sue the airport for losing my luggage. I lost my case.',
    "I asked the librarian if the library had any books on paranoia. She whispered, 'They're right behind you.'"],
    _dadJokes: ['What do you call fake spaghetti? An impasta!',
    "Why don't skeletons fight each other? They don't have the guts!",
    'I told my wife she should embrace her mistakes. She gave me a hug.',
    'Did you hear about the kidnapping at the playground? They woke up.',
    "Why don't eggs tell jokes? They'd crack each other up!"],
    _oldJokes: ['I told my wife she was getting old. She went to go get a second opinion and never came back.',
    'I told my doctor I broke my arm in two places. He told me to stop going to those places.',
    "I don't need a hairstylist. My pillow gives me a new hairstyle every morning.",
    "I asked my grandpa how he's feeling. He said, 'With my hands.'",
    "I asked my grandma if she ever tried walking in my shoes. She said, 'No, but I've tried kicking you out of them!'"],
    _religionJokes: ['What do you call a gathering of atheists? A non-prophet organization!',
    "Why don't atheists solve exponential equations? Because they don't believe in higher powers!",
    "Why did the atheist go to church? To catch some z's during the sermon!",
    'How many atheists does it take to change a light bulb? None, they prefer to see the light of reason!',
    "Why don't atheists attend church bake sales? Because they have a 'no prophet' margin policy!"],
    _kidJokes: ['Why did the scarecrow win an award? Because he was outstanding in his field!',
    'What did one ocean say to the other ocean? Nothing, they just waved!',
    'Why did the bicycle fall over? Because it was two-tired!',
    "What's orange and sounds like a parrot? A carrot!",
    "What do you call cheese that isn't yours? Nacho cheese!"],
    _jokes: [this._edgyJokes, this._dadJokes, this._oldJokes, this._religionJokes, this._kidJokes],

    get edgyJoke() {
        let i = Math.floor(Math.random() * this._edgyJokes.length);
        return this._edgyJokes[i];
    },
    get dadJoke() {
        let i = Math.floor(Math.random() * this._dadJokes.length);
        return this._dadJokes[i];
    },
    get oldJoke() {
        let i = Math.floor(Math.random() * this._oldJokes.length);
        return this._oldJokes[i];
    },
    get religionJoke() {
        let i = Math.floor(Math.random() * this._religionJokes.length);
        return this._religionJokes[i];
    },
    get kidJoke() {
        let i = Math.floor(Math.random() * this._kidJokes.length);
        return this._kidJokes[i];
    },

    // THIS METHOD IS THE PROBLEM CHILD
    randomJoke() {
        let categoryIndex = Math.floor(Math.random() * this._jokes.length);
        let category = this._jokes[categoryIndex];
        let jokeIndex = Math.floor(Math.random() * category.length);
        return category[jokeIndex];
    }
};
The test `console.log()`s:
// FOR CATEGORICAL JOKES

/*
console.log(objJokes.edgyJoke);

console.log(objJokes.dadJoke);

console.log(objJokes.oldJoke);

console.log(objJokes.religionJoke);

console.log(objJokes.kidJoke);
*/

//FOR A SINGLE RANDOM JOKE
console.log(objJokes.randomJoke());

Thank you for taking a look! Hoping I can get this figured out.

Next time, please post the error you are getting aswell, since the alternative is that each peroson who sees the post needs to run the whole code when sometimes it can just be seen from the error if you have experience.

this is a reference to the object/class itself and its only created after the object has been intializated/created.

So the problem in you code is that when you try to create the nested array _jokes you are accesing with this._[joketype] for each of the arrays, but at that monent, they still don’t exist, so your nested array is initialized as an array of undefined values

A simple solution is to just simply declare all those arrays as constants outside of your class and then use them to intialize you _jokes array

2 Likes

Thank you for your response. I had been in a rather big rush to pick up a friend, and I don’t usually get a response this fast. But it was my intent to rerun the code and post the errors at this time, so here it is. This error:

[Running] node "c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js"
c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:52
        let jokeIndex = Math.floor(Math.random() * category.length);
                                                            ^

TypeError: Cannot read properties of undefined (reading 'length')
    at Object.randomJoke (c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:52:61)
    at Object.<anonymous> (c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:67:22)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.11.0

for this code:

randomJoke() {
        let categoryIndex = Math.floor(Math.random() * this._jokes.length);
        let category = this._jokes[categoryIndex];
        let jokeIndex = Math.floor(Math.random() * category.length);
        return category[jokeIndex];
    }

And this error:

[Running] node "c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js"
c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:51
        let j = Math.floor(Math.random() * this._jokes[i].length);
                                                          ^

TypeError: Cannot read properties of undefined (reading 'length')
    at Object.randomJoke (c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:51:59)
    at Object.<anonymous> (c:\Users\jayyy\OneDrive\Desktop\Projects\JS Portfolio Project\index.js:66:22)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.11.0

with this code:

randomJoke() {
        let i = Math.floor(Math.random() * this._jokes.length);
        let j = Math.floor(Math.random() * this._jokes[i].length);
        return this._jokes[i][j];
    }

Looking over your response, I’m not sure I properly understand the concept of initialization at this point in my Javascript education. Or how it would apply in this scenario. I would appreciate a more in-depth explanation, but if not that’s okay; you’ve at least given me something to look into.

The explanation by @0xhepha is correct, but I would suggest another solution: Turn your _jokes property into a getter and return the array as you created it.

1 Like

This is declaring a variable, to put it simple, its just naming it or bringing it to existance:

var name;
let age;
const username;

But if you log any of these variables you will get undefined. They exist ( they are defined) but they are empty.

Now, this is defining or intializing a variable:

name = "Hepha"
age = 404
username = "0xHepha"

So now if you log them, they will have log something

Normally we do both action combined:

var name = "Hepha"
let age = 404
const username = "0xHepha"

Regarding you issue, lets put a simple example first:

let doubleNumber = number * 2
let number = 4

In this case we will have a Refference Error since doubleNumber is trying to use number variable that doesn’t exist due to the fact that the code ( in general) runs from top to bottom.

Where does the error in your code come from?

Objects and classes even if they look like a lines of code, actually they are pure syntax, long one, but still its just sytanx. So even if they are large they are still the same as doing {title: "aaaah" , funciton(){some code...}, etc}

Why do I mention this?

Code executes from top to bottom ( in general) but each type or chunk of code is executed at the same type by interpreter or compiler. When an array is created, the whole data is stored at the same time (even if at low level the code iteracts with elements one by one) , so for the coder it looks like it hapens all instantly. The same happens with objects, even if the objects is inmense, the whole object is created and stored at the same time, its not like the properties or funcitons are added one by one, but all at the same.

this. is a reference to the object itself BUT the object doesn’t exist when its being created, it only exist after it has been created.

Why does it work inside functions but no for object attributes?

A function is a chunk of code that runs when it has been called, but to call a function from an object, the object exists already, so this. points or references an object

But the object properties/variables are asigned during the object creation (not when called like functions) and during object creation there is no this becuase there is no object yet.

1 Like

The sugestion of using pure variables outside for the creation of object/class is due to the fact that usually such heavy data amount ins’t stored in plain text inside the code, it comes from an API or a Database. In this way it migh help him pick the habit of keeping the code clean

1 Like

You are a rockstar. Sincerely, thank you for the in-depth explanation. I stepped away from Javascript for some time, and unfortunately I lost some of the fundamentals, which I am trying to regain now. Responses like yours are very helpful to me. If I may ask one more thing of you, could you provide an example of how you would revise my code? I’m a visual learner. It’s a lot of work though, so I wouldn’t blame you if you’d rather not. Thanks again!

I tried turning it into a getter, per your advice, but would you mind clarifying what you mean by ‘return the array as you created it’?

As I comented in a previous reply, I would try to have all that text outside of code, normally, big chunks of data will we obtained from a database, an API or just importing a json file. So even in this case to prepare for it and also keep a good practice of having everything in order.

  1. Keep all of the data outside of the class as simple constants and the object initalization just uses this constants, if in the future the data is obtained from a diference source, then the work is only a variable name change istead of removing all the text.
  1. A getter or setter doesn’t necesarilly mean that it afects the variable that is named after, so in this case I wouldn’t use a inner variable for each type of joke and the main probelm being the fact that you get duplicated data stored

  2. I would have all the data at the top of the file/module as nested array of arrays jokes and each index represents a joke type

  3. To avoid errors I would use a index object at the top

const jokeType = {
    "edgy" : 0,
    "dad" : 1,
    "old" : 2,
    "religion" : 3,
    "kid" : 4,
}

And then the constant that keeps the jokes data:

// be carefull of the index of each type of joke, this array could be created with a loop to make it exact, but lets keep it simple for this case
const jokes = [[...],[...],[...],[...],[...]]

So then you can use that constant for the _jokes atribute and also use the jokeType indexes for the getters:

const objJokes = {
_jokes : jokes,

 get edgyJoke() {
        let typeIndex = jokeType["edgy"]
        let i = Math.floor(Math.random() * this._jokes[typeIndex].length);
        return this._jokes[typeIndex][i];
    },

}

Here’s what I meant – a quick fix:

get _jokes() {
  return  [this._edgyJokes, this._dadJokes, this._oldJokes, this._religionJokes, this._kidJokes]
}
1 Like

I get the point of not having hard coded data inside the object. Simulating dynamic data with variables outside the object makes sense – maybe not at the current stage, though.
But with your suggestion, the data has to be maintained triple, that’s not sustainable.

Why this hard coded object of indexes? What if the order of the nested jokes array changes:

Then a hard coded getter here. Three items to maintain for a singe change:

1 Like

That is exactly the point of having this indexer object, if the order of the nested array changes, then the only thing that needs to be changed is this object, nothing else, since the rest of the code uses the indexes specified by this object. It can be added, removed or modified based on the array information.

Lets say that there is only the nested array withouth the indexer, then to get the first joke of each category you would need to do :

let edgyJoke = this._jokes[0][0]
let dadJoke = this._jokes[1][0]
let oldJoke = this._jokes[2][0]
let religionJoke = this._jokes[3][0]
let kidJoke = this._jokes[4][0]

or with the indexer:

let edgyJoke = this._jokes[jokeType.edgy][0]
let dadJoke = this._jokes[jokeType.dad][0]
let oldJoke = this._jokes[jokeType.old][0]
let religionJoke = this._jokes[jokeType.religion][0]
let kidJoke = this._jokes[jokeType.kid][0]

As you said, imagine that the nested array changes, in the code that uses the indexer, I only need to make some changes in the indexer object, while in the first example lines, I need to change the whole code. In this case it doesn’t make a big difference due to the fact that its only some number changes, but in the class jokes object you would need to change all the functions that uses the array it they don’t use the indexer.

In fact, the getter its not hardcoded at all, it doesn’t pick a specific position, it uses a reference. Imagine that I change the nested array and I add “bad” jokes at the begining, then I only need to do this:

const jokeType = {
    "bad": 0
    "edgy" : 1,
    "dad" : 2,
    "old" : 3,
    "religion" : 4,
    "kid" : 5,
}

And the rest of the code works completlly fine withouth the need to change anything at all

On other hand it also helps for code mantainance and redeability in the future as it easier to use direcctly a name than remembering postion for each name

1 Like

That’s one too much. If you get your jokes from a form or api and you have to change an object manually, your code is not generic.

And if a category ‘bad’ is added afterwards, you have to manually add a getter get badJoke().
If you want to be generic, be generic.

One possible way to achieve that is having a nested object rather than a nested array for jokes:

const jokes = {
 	_jokes: {},
   getJokeByCategory(category) { ... }
   addJokes(jokesObj) {
     this._jokes = {
   		...this._jokes,
        ...jokesObj
    }
  },
   ...
}

jokes.addJokes({edgyJokes: [...]})
1 Like

I did a sugestion on how to solve his actual code withouth much change to its structure. The point wasnt to make the most generic class posible, it was to solve a issue. I just did a small implementation. This is the never ending issue with programing where everyone has its preferences

2 Likes

I found both preferences/opinions to be helpful to my learning. I appreciate y’all hashing things out here. I will look over what you have both said and see how I can improve my code.

1 Like