Cannot read property 'id' of undefined

Here’s some code:

<!Doctype html>
<html>
	
	<title>
		Tic Tac Toe!
	</title>
	
	<style>
		
		*
		{
			margin: 0 auto;
			padding: 0;
		}
		
		html, body
		{
			position: relative;
			width: 100%;
			height: 100%;
		}
		
		h1
		{
			position: absolute;
			font-family: "Impact";
			top: 0;
			right: 0;
			bottom: 0;
			left: 0;
			margin: auto;
			text-align: center;
		}
		
		#container
		{
			position: absolute;
			width: 300px;
			height: 300px;
			top: 0;
			right: 0;
			bottom: 0;
			left: 0;
			margin: auto;
		}
		
		.box
		{
			width: 33.33%;
			height: 33.33%;
			background-color: #009900;
			cursor: pointer;
		}
		
		.box:hover
		{
			background-color: #008000;
		}
		
		.box:active
		{
			background-image: url("X.png");
		}
		
		.rightBox
		{
			float: right;
		}
		
		.midBox
		{
			float: right;
		}
		
		.leftBox
		{
			float: left;
		}
	</style>
	
	<body onload = "startGame()">
		
		<h1 id = "turnStatus"></h1>
		
		<div id = "container">
			<div id = "box1" class = "box leftBox"></div> <div id = "box3" class = "box rightBox"></div> <div id = "box2" class = "box midBox"></div>
			<div id = "box4" class = "box leftBox"></div> <div id = "box6" class = "box rightBox"></div> <div id = "box5" class = "box midBox"></div>
			<div id = "box7" class = "box leftBox"></div> <div id = "box9" class = "box rightBox"></div> <div id = "box8" class = "box midBox"></div>
		</div>
		
		<script>
			function startGame()
			{
			
				var boxClass = document.getElementsByClassName("box");
				
				var turnStatus = document.getElementById("turnStatus");
				
				var yourTurn;
				
				function displayTurnMessages()
				{
					if(yourTurn === true)
					{
						turnStatus.innerHTML = "PLAYER 1 TURN";
					}
					
					else if(yourTurn === false)
					{
						turnStatus.innerHTML = "PLAYER 2 TURN";
					}
				}
				
				function shuffleTurn() //whoever goes first will be randomly chosen
				{
					var turn = Math.floor(Math.random() * 2) + 1; //choose random number between 1 and 2
					
					if(turn === 1) //if 1, your turn
					{
						yourTurn = true;
					}
					
					else if(turn === 2) //if 2, player 2 turn
					{
						yourTurn = false;
					}
				}
				
				var offLimitsList = new Array(); //if anyone wants to move to a position in this list, it won't let them
				
				function isOffLimits(selectedDivId)
				{
					
					var isOffLimits = false;
					
					for(var i = 0; i < offLimitsList.length; i++)
					{
						if(offLimitsList[i].id === selectedDivId)
						{
							isOffLimits = true;
						}
					}
					
					return isOffLimits;
				}
				
				for(var i = 0; i < boxClass.length; i++)
				{
					boxClass[i].addEventListener("click", function()
					                                      {
															if(isOffLimits(boxClass[i].id) != true) //if isOffLimits(div.id) != true, change background and background color
															{
																boxClass[i].style.backgroundColor = "#008000";
																boxClass[i].style.backgroundImage = "url(\"X.png\")";
															}
															
														  });
				}
				
				function placePiece()				//when called, this should show an x or o in the box
				{
					
					if(yourTurn === false)
					{
						
					}
				}
				
				function alternateTurn()
				{
					if(yourTurn === true)
					{
						yourTurn = false;
					}
					
					else
					{
						yourTurn = true;
					}
				}
				
				function identifyWin()
				{
				} 
			}
		</script>
	</body>
</html>

If you run it in chrome and click one of the divs, it produces an error on line 151 saying that it cannot read property ‘id’ of undefined (as if to say that boxClass[i] is undefined). However, I know that boxClass[i] will be equal to a div (I tested this by using a for loop to print the values to the console). So, why am I getting this error?

You can use ‘this’ to identify ‘the thing that popped the event’ in this case: if you replace boxClass[i] with this into the addEventListener function it would works^^

You can figure out the reason of the error putting a console.log(i) into your function: when you click in a box the console log i=9.
Why?
Because you went from 0 to 8 while adding the event listener, when i=9 it ends the loop but if you use the value of i again after the loops ended ( as you’re trying to do accessingboxClass[i] on click) it’s still 9 :slight_smile:

To solve this mistery you have to understand the scopes and this identifier.

  • When you declare a for-loop var for(var i = 0; cond; i++) the i will be added to the scope the for-loop runs in, which means for your code, the i is attached to the startGame function.

  • The for statement is defined like this

for ([initialization]; [condition]; [final-expression])
   statement

And the final-expression is evaluated at the end of the loop before checking the condition
That’s how you have i = 9 at the end of the loop

For loop from MDN

  • The this keyword refers to a context. Inside a callback function like yours
boxClass[i].addEventListener("click", function(event) {
    this
});

this is the current clicked DOM element, it’s an equivalent of the boxClass[i] you’ve attached the event to !
You can access its properties like you have already done, this.id

Thanks guys, I’ve learned alot

1 Like