DEV Community

Naftali Kulik
Naftali Kulik

Posted on

Academic vs Intuitive understanding (aka practice makes competent)

Welcome to my first blog post!

I am just starting out on my journey as a student at Flatiron School, and I'd like to share a few insights that I've picked up so far along the way. Specifically, I'd like to discuss how much there is to gain from practicing your code and seeing how it works over just learning the rules, syntax, etc.

One thing that jumps out at me, again and again, is the vast gap between having knowledge (i.e. academic understanding) of something and actually getting (intuitive understanding) something in a way that the knowledge can be applied properly and practically. This in fact appears to be a major difference between the way Flatiron School teaches and the way many college courses are taught. Your average college course may teach you all there is to know about programming; Flatiron School makes you into a programmer. The difference is subtle but profound.

I'll give a quick example of this to demonstrate what I mean. It's hard for me to imagine now, but when I completed my prework for Flatiron I had no idea how iteration/looping could be useful. That's not to say that I didn't understand the basics of how it worked, but I hadn't intuitively grasped how it could actually be applied in a meaningful fashion. Because of this, when I was creating a website for my prework project, it never occurred to me that this:

flipCards(lineOne, 0, 0);
flipCards(lineOne, 1, 1);
flipCards(lineOne, 2, 2);
flipCards(lineOne, 3, 3);
flipCards(lineOne, 4, 4);
flipCards(lineOne, 5, 5);
flipCards(lineTwo, 0, 6);
flipCards(lineTwo, 1, 7);
flipCards(lineTwo, 2, 8);
flipCards(lineTwo, 3, 9);
flipCards(lineTwo, 4, 10);
flipCards(lineTwo, 5, 11);
flipCards(lineThree, 0, 12);
flipCards(lineThree, 1, 13);
flipCards(lineThree, 2, 14);
flipCards(lineThree, 3, 15);
flipCards(lineThree, 4, 16);
flipCards(lineThree, 5, 17);
flipCards(lineFour, 0, 18);
flipCards(lineFour, 1, 19);
flipCards(lineFour, 2, 20);
flipCards(lineFour, 3, 21);
flipCards(lineFour, 4, 22);
flipCards(lineFour, 5, 23);
Enter fullscreen mode Exit fullscreen mode

with only a few minor modifications to my code could have looked like this:

for (let i = 0; i < allCards.length; i++) {
    flipCards(i)
}
Enter fullscreen mode Exit fullscreen mode

Now I rarely write code without using some form of iteration or looping but at the time it wasn't so simple.

So how do you make the jump? How do you go from academic knowledge to intuitive understanding? I've found the answer to be practice, or more specifically making mistakes. "Learning from your mistakes" is a cliché that may be older than computer programming itself, but rarely can it be applied quite as literally as it can when it comes to coding. By working through your code and figuring out what it's actually doing vs what you think it should be doing you can gain more than you ever would by "book" learning alone. Allow me to take you through some code that's based on code that I wrote, and demonstrate how at every step every mistake I made led me to a deeper and more complete understanding of the code I was working with.

Say we have an object with car manufacturers as its keys, some with empty strings as values, some with an array containing some of the models of car that are produced by that company:

const cars = {
    honda: '',
    toyota: ['corolla', 'camry', 'avalon', 'sienna'],
    gmc: '',
    chrysler: '',
    ford: ['explorer', 'fusion'],
    hyundai: ['elantra', 'sonata', 'genesis', 'santa fe'],
    infiniti: ''
};
Enter fullscreen mode Exit fullscreen mode

Now let's say I'd like to render these cars as a <ul> on my webpage, with a nested <ul> to list the models of car that are included for those manufactures that have them. If we were just making a list of manufacturers (the keys) that would be fairly simple. We have a <ul> in our html file with an id of #main-list. Let's build a function that would create a <li> tag and add it to this <ul>:

function makeLi(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
}
Enter fullscreen mode Exit fullscreen mode

Now we can use a simple for...in to create the list:

for (const car in cars) {
    makeLi(car)
}
Enter fullscreen mode Exit fullscreen mode

Now let's see what we get:

Image description
That was easy!

However, we'd like to add the nested <ul> so let's see how we can do that.

First let's add another parameter to makeLi:

function makeLi(car, id) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById(id).appendChild(li)
}
Enter fullscreen mode Exit fullscreen mode

Everything is the same, except that the id of the <ul> is passed as an argument instead of being spelled out in the function.

Now we'll start building another function. This one will start off the same as makeLi but will have a few extra steps:

function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = 'child-ul'
    li.appendChild(ul)
    cars.car.map(model => makeLi(model, 'child-ul'))
}
Enter fullscreen mode Exit fullscreen mode

Now let's update our for..in with a simple ternary statement:

for (const car in cars) {
    cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Enter fullscreen mode Exit fullscreen mode

And let's see what we get:

Image description
Well... Seems like we just ran into our first learning opportunity! The error message is telling us that JavaScript apparently doesn't recognize our .map() method for what it is, rather it seems to be trying to give the word "map" some sort of value (which doesn't exist). It must be that cars.car, which should return the value of whichever key our for..in loop is passing as an argument to the function, is not being recognized as an array for some reason. Let's use console.log() to do some troubleshooting.

for (const car in cars) {
    console.log(cars.car)
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Enter fullscreen mode Exit fullscreen mode

Let's take a look at our console to see what we've gotten:

Image description
That's weird... It seems that cars.car is returning undefined. Have we misunderstood something about our for..in loop? We'll quickly make sure we haven't missed something here:

for (const car in cars) {
    console.log(car)
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Enter fullscreen mode Exit fullscreen mode

Let's check the console:

Image description
It seems that car does in fact return the keys to our cars object, so why is cars.car undefined? We've just learned our first lesson! Now I've realized the implications of something I already knew but perhaps never internalized the way I needed to. All keys are really strings. Our for..in is saving the keys of cars to the car variable as strings, and therefore our dot notation isn't working! In fact, if we look at our DOM, it seems the ternary expression didn't work properly either:

Image description
It seems that our <li> for honda has a nested <ul> despite its value in our cars object being an empty string. Our ternary statement was checking if cars.car === '' and was returning false, because cars.car was in fact undefined! Our code therefore called the makeLiWithNestedUl function on the first key of our cars object, honda, and then ran into an error when we tried using the .map() method on cars.car, which was undefined. Let's do a quick console.log(), this time with bracket notation, to confirm our suspicions:

for (const car in cars) {
    console.log(cars[car])
    //cars.car === '' ? makeLi(car, 'main-list') : makeLiWithNestedUl(car)
}
Enter fullscreen mode Exit fullscreen mode

And here's the result:

Image description
Perfect! It's logging the correct values for all of our keys! (In some cases that's nothing, i.e. an empty string.)

Let's get rid of the console.log(), uncomment our ternary statement, change our dot notation to bracket notation (both in the ternary and in the makeLiWithNestedUl function), and see what happens!

Image description
Oy vey. Our bracket notation seems to have fixed our ternary statement and gotten our .map() method to work. However, for some reason, all of the nested <li> elements are being added to the first nested <ul>, under toyota. Let's take a look at our DOM to see if ford and hyundai aren't getting their <ul>s for some reason.

Image description
Hmmm. They definitely are getting nested <ul>s added, but I think our mistake is fairly obvious now. We've accidentally assigned the same id to all of our nested uls. There are two lessons to be learned here. First of all, although an html document isn't supposed to have more than one element with the same id, JavaScript apparently has no problem adding the same id to more than one DOM node if we tell it to. Secondly, if more than one DOM node or element has the same id, document.getElementByID() will return the first element with that id. Let's look at our functions again:

function makeLi(car, id) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = 'child-ul'
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, 'child-ul'))
}
Enter fullscreen mode Exit fullscreen mode

Our makeLiWithNestedUl is calling makeLi and passing 'child-ul' as the argument for the id parameter. Because of this, all of our nested <li>s are being added to the first <ul> with an id of 'child-ul', which is the one for toyota.

How can we assign different ids to our nested <ul>s while still effectively using iteration? Here's where we can use a simple tool that's way more powerful than I realized when I first learned about it. Some simple string interpolation should allow us to assign unique ids to each nested <ul>:

ul.id = `${car}-list`
Enter fullscreen mode Exit fullscreen mode
cars[car].map(model => makeLi(model, `${car}-list`))
Enter fullscreen mode Exit fullscreen mode

And here's our updated function:

function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`))
}
Enter fullscreen mode Exit fullscreen mode

Let's refresh our browser and see the result:

Image description
We did it!

Let's go a bit further. There's more code to write, more mistakes to be made, and more lessons to learn!

I'd like to be able to change the color of each car manufacturer name to blue when it's clicked on, and each car model name to red when its clicked on. I'd also like for both to be changed back to black when clicked again. Should be simple, right? We can create a relatively simple event listener. First let's modify our original functions a bit:

function makeLi(car, id, className) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = className
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = 'parent-li'
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`, 'child-li'))
}
for (const car in cars) {
    cars[car] === '' ? makeLi(car, 'main-list', 'parent-li') : makeLiWithNestedUl(car)
}
Enter fullscreen mode Exit fullscreen mode

What I did here was add a parameter to my makeLi function allowing me to assign a className to the <li> element being created. This allows me to to assign separate classNames for the parent and child <li> elements. Lets make some arrays now:

const parentLiArray = Array.from(document.getElementsByClassName('parent-li'))
const childArray = Array.from(document.getElementsByClassName('child-li'))
Enter fullscreen mode Exit fullscreen mode

Now let's add some event listeners:

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
    })
})
Enter fullscreen mode Exit fullscreen mode

Great! now let's check the result:

Image description
Oops. We forgot that styles applied to a parent will also apply to the child unless otherwise specified. When we click on hyundai it's also turning its child elements blue. Let's give a default style to all of our <ul> elements to prevent this:

Array.from(document.getElementsByTagName('ul')).map(ul => ul.style.color = 'black')
Enter fullscreen mode Exit fullscreen mode

Image description
There we go! Uh-oh. We seem to be running into other issues though.

Image description
Why are our nested <li>s also turning blue? To make matters worse, they're not changing back to black when they are clicked again. Let's get some console.log()s going again.

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
        console.log(e.target.style.color)
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        console.log(e.target.style.color)
    })
})
Enter fullscreen mode Exit fullscreen mode

Now we can check the console for some clues as to what's going on:

Image description
Now that's interesting. There are two values being logged when the nested <li> is being clicked. The first is red, as it should be. The second is blue. Apparently our code is changing the color to red as it should, but then immediately changing it to blue. Clicking it again logs the same result. (You can take my word for it). Let's try a different console.log() to see if our event listeners are behaving the way they should:

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'blue' ? li.style.color = 'black' : li.style.color = 'blue'
        console.log(e.target)
    })
})

childArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        console.log(e.target)
    })
})
Enter fullscreen mode Exit fullscreen mode

Let's check out the console again:

Image description
Fascinating. The clicked element is being logged twice! Making this mistake originally taught me something I actually hadn't known. Adding an event listener to a parent element will automatically add the same event listener to its children. When I clicked the child <li> element, it first fired its own event listener, which checked if its style was set to red (it wasn't) and then changed it to red. That part worked perfectly. The problem was that after that the event listener for the parent fired, checked if the style was blue (it wasn't), and changed it to blue. The reason it didn't change back when clicked again was that it kept repeating the same process, checking if it was red, changing it to red, then checking if it was blue and changing it to blue. Now that we realize this maybe we only need one event listener for both! How can we do that? Let's see... We need some way for JavaScript to differentiate between the parent and child elements and act accordingly. I'm going to get rid of the event listener for the child <li> elements because we don't need it anymore. Now let's change up our other event listener a bit:

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        if (li.className === 'parent-li') {
            li.style.color === 'blue' ? li.style.color = 'black' : li.style.color ='blue'
        } else {
            li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

Now our event listener is first checking the className of its target and acting accordingly, changing between blue and black if its className is 'parent-li', and changing between red and black if not. Let's try it in our browser again:

Image description
Great! Now let's click a few of them again to make sure they change back to black:

Image description
Perfect! We did it! Here's the completed, functioning code:

const cars = {
    honda: '',
    toyota: ['corolla', 'camry', 'avalon', 'sienna'],
    gmc: '',
    chrysler: '',
    ford: ['explorer', 'fusion'],
    hyundai: ['elantra', 'sonata', 'genesis', 'santa fe'],
    infiniti: ''
};

function makeLi(car, id, className) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = className
    document.getElementById(id).appendChild(li)
}
function makeLiWithNestedUl(car) {
    const li = document.createElement('li')
    li.textContent = car
    li.className = 'parent-li'
    document.getElementById('main-list').appendChild(li)
    const ul = document.createElement('ul')
    ul.id = `${car}-list`
    li.appendChild(ul)
    cars[car].map(model => makeLi(model, `${car}-list`, 'child-li'))
}
for (const car in cars) {
    cars[car] === '' ? makeLi(car, 'main-list', 'parent-li') : makeLiWithNestedUl(car)
}

Array.from(document.getElementsByTagName('ul')).map(ul => ul.style.color = 'black')

const parentLiArray = Array.from(document.getElementsByClassName('parent-li'))

parentLiArray.map(li => {
    li.addEventListener('click', e => {
        const li = e.target
        if (li.className === 'parent-li') {
            li.style.color === 'blue' ? li.style.color = 'black' : li.style.color ='blue'
        } else {
            li.style.color === 'red' ? li.style.color = 'black' : li.style.color = 'red'
        }
    })
})

Enter fullscreen mode Exit fullscreen mode

I've now demonstrated just one small example of how practicing code, messing up, and figuring out what went wrong is the best and perhaps only way to truly understand and internalize how your code is really working. The mistakes I made in this example are based on real mistakes I made and the steps I took to fix them. (It was quite frustrating at the time, let me tell you!) I am just starting out on my journey as a programmer and I am looking forward to many future mistakes! Thanks for hanging in there with me until the end of my first ever blog. Any and all feedback is welcome!

Top comments (0)