Applying Hooks! 🎣

Now that the styling and components have been laid out, let’s start hookin’ . We already have one state variable, position , to represent the position of the snake on the grid, but we’ll need another, direction , to indicate in what direction the head is moving. We’ll also have to use an effect to create listeners for keypresses that will change the value of direction .

We’ll invoke useState and pass in an initial value of right for our direction variable. We pass in a callback to useEffect that puts a listener on the keydown property on our document body . It filters for arrow key presses, and uses setDirection to change the value accordingly. Also note, we’re only running this effect once — on line 24 we pass in an empty array as the 2nd argument to useEffect , informing it there are no deps it should change upon.

Now to make the snek move. the values in the array the variable position hold determine where on the grid the snake head will appear. The first index represents the row, and the second represents the column. In snake, the head continuously moves in a direction determined by the player. This means, at a set interval, the values in the position array will have to be updated.

Arrays to pass into `setPosition` to move in each direction

In hooks-speak, this translates to an effect that kicks off a setInterval that will call setPosition with new values that represent the new position of our snek. We will want to call this effect every time the direction value changes, so we can pass in an array with the proper incremented/decremented row/column values. Since we’ll be calling this effect many times, we’ll want to cleanup each time, to remove the setInterval from our window .

Let’s bang this out. We’ll add this second effect, and inside the callback, we’ll call our setInterval and assign it to a variable. We’ll return a cleanup function that calls clearInterval . Inside the callback, let’s write a switch statement to check the values of direction , and pass an array into setPosition that represents a step in a given direction. Let’s throw a console log in there too, to print the value of position , because the the code I just laid out won’t function as we are thinking, because I’m trying to drive home a point 😏.

Check out the app in the browser. Refresh the page. You’ll notice our snake only moves once then stops. If you hit one of the arrow keys, the same thing will happen — it’ll move once then stop. Dafuq? We’ve set an interval to run a function that calls setPostion once a second; it’s strange that it only seems to run once.

Open the dev tools. You’ll see our log printing the position , and you’ll see it keeps printing the same coordinates. The nature of hooks is that each render is a “snapshot” in time. And at the snapshot that our setInterval was invoked, our position was a specific set of numbers. That’s just a dramatic way of saying that our variables don’t change until we explicitly tell them to.

0 0 5evr 😭

At the time of calling our setInterval callback, we frame position at whatever set of numbers that position is set at (0, 0 at first). Luckily, we have a way to hack the matrix; a tool in our arsenal that transcends time and space to get the true current value of position — the ability to pass a callback into setPosition . Just like setState accepts either an object literal or a callback whose argument passed in was a copy of our current state, our setPosition function has the same option available to it. The argument passed into the callback isn’t concerned with the snapshot — it holds the true, current value of position , and we can use this to fix our issue.

Passing in callbacks allows us to reference our closed-over state. While variables in the snapshot, like position , are immutable, the value passed into the callback is certainly not. Here, we destructure the argument, and return a new array, incrementing/decrementing either x or y depending on the value direction holds. cmd+tab back to that browser and watch the magic✨.

he move

You’ll notice once you run into a wall, your snake just disappears. This is because it’s out of bounds — there are no tiles on the grid whose values match the values in position . In real snake, if you go out of bounds, the game resets, and your head moves back to the original position. We can provide that function pretty easily.

With the added ternary, we check to make sure the head isn’t outside the defined bounds of our grid. If it is, we set position back to [0,0] . If not, it’s business as usual.

All is seemingly well. But use the arrow keys to move in another direction and look closely. Sure, it moves in the applied direction, but there seems to be an odd, variable delay. Why would this be? Look at the second argument we pass into the useEffect , the array that holds our deps. The array holds direction , so that every time the value of direction changes, the callback passed into useEffect is called again. This means that whenever we change direction, our interval is cleared and a new one is set. This is why the movement is not smooth — our timer is reset mid-interval; this creates the effect of a longer than usual delay.