Closure and event listeners
Tags: webdev, javascript, beginners
Published: 2023-01-06 21:40:14

Closure is notorious as one of the hardest concepts for Javascript beginners to pick up. That would be fine if it surfaced only rarely or in advanced applications. But closure plays a huge (and under-appreciated) role in Web Development 101. If you've ever added an event listener to handle a button click or a form submission like so: `myElement.addEventListener('click', callbackFunction)`, you've likely been leveraging the power of closure.

Let's dive in.

## Introducing closure 

Closure is a hidden superpower of functions in Javascript and other programming languages. When thinking about closure, the mantra that I repeat to myself over and over again is:

> Functions retain access to variables in the context in which they were created.
> Functions retain access to variables in the context in which they were created.
> Functions retain access to variables in the context in which they were created.
> ...

Set aside the technical details for a second. More on those to come. One of the biggest practical takeaways, especially for the budding developer, is that functions can, if created in the right context, access data that might seem "lost." _You_ may have neglected to store a piece of data by assigning it to a global variable, but it's possible that one of your functions did not.

The killer use case for this in basic web development is achieving front-end persistence without a lot of awkward global variables. But first let's go deeper into what closure is.

## A non-web example of closure

A classic simple illustration of closure begins with function that returns another function. 

```
let count = 0;
function outer(a){
  function inner(){
    count++;
    console.log(count)
    return a * count;
  }
  return inner;
}
const ourFunction = outer(5)
ourFunction() // =>1
ourFunction() // =>2
ourFunction() // =>3
```

Here we are defining a function `outer` that itself defines a function called `inner` and returns it. `inner` increments the global variable `count`, console.logs it, multiplies it by the value of the parameter `a`, and finally returns that result. 

When we call `outer` with the argument 5, it defines a function in the global scope named `ourFunction`. So `ourFunction` returns the next multiple of 5 each time it is called. Moreover, repeatedly calling `ourFunction` will log the value of `count` incremented by 1 each time. So `ourFunction` seems to be doing a useful service not only of counting by five but also of tracking the number of times it has been invoked.

All of this should be relatively straightforward. Since `count` is a global variable, it can be accessed from inside `outer` and for that matter from inside `inner` as well!

But `count` is maybe a little _too_ accessible. What if we had some friends who wanted to use `outer` to do their own counting by 7s. We could try something like this.

```
let count = 0;
function outer(a){
  function inner(){
    count++;
    console.log(count)
    return a * count;
  }
  return inner;
}
const ourFunction = outer(5)
ourFunction() // =>1
ourFunction() // =>2
ourFunction() // =>3

const theirFunction = outer(7)
theirFunction() // =>4
theirFunction() // =>5
theirFunction() // =>6

ourFunction() // => 7
```

The global variable `count` now gets incremented by both `ourFunction` and `theirFunction`. The first time `theirFunction` gets called it logs 4 and returns 28. We have lost the desirable feature of counting up smoothly by 5s or 7s and of tracking the number of times each specific function was invoked.

What we would like to have is a `count` variable specific to `ourFunction` and another `count` variable specific to `theirFunction`.

We could define two global variables, `ourCount` and `theirCount`. But what if we wanted to count by 9s, 11s, 13s, and so on? We could create a new global variable each time, but then we could wind up with hundreds or thousands. 

What to do?

We know from Javascript 101 that invoking a function creates its own execution context. Variables defined in that context are not accessible once the function returns a value: if you declare `myVar` within a function, you don't then get to access `myVar` outside that function. But this seems to deepen the mystery. Shouldn't a variable be either global (which won't work for us) or else confined to fleeting execution context where we can't get to it?

Let's go back to our mantra:

> Functions retain access to variables in the context in which they were created.

Unpacking this, we know:

1. `ourFunction` retains access to variables in the context in which it was created. 
2. It was created in the execution context of the function `outer`. 
3. So any variable declared in the scope of `outer` would be accessible to `ourFunction`. 

Wouldn't that same variable be accessible to `theirFunction` as well? Well, no, not the same variable, but a similar variable with the same name, because each time `outer` is invoked it defines a new execution and `count` is declared anew in that context and functions declared in the same context would have access to it! This seems to be exactly what we want. 

This line of thought suggests that we simply declare `count` inside `outer`. 

```
function outer(a){
  let count = 0;
  function inner(){
    count++;
    console.log(count)
    return a * count;
  }
  return inner;
}
```

And indeed this gives us what we want.

```
const ourFunction = outer(5)
ourFunction() // =>1
ourFunction() // =>2
ourFunction() // =>3

const theirFunction = outer(7)
theirFunction() // =>1
theirFunction() // =>2
theirFunction() // =>3

ourFunction() // => 4
```

Each time we invoke `outer` to create a function, we are also creating a locally-scoped `count` variable that `inner`, which is created in the same context, can access. We are creating a `count` variable that is _enclosed_ within that function. 

## Achieving front-end persistence

This may all seem like a curiosity or a programming exercise. How often do we actually define functions within functions? 

It turns out to be surprisingly common. Any time you have a set of elements on a website--images, buttons, names in a list--and want to add the same functionality to each one, you are like to define a function that iterates over each element. And within that function you are like to call the `addEventListener` method which itself takes a function, the so-called callback, has an argument. So for each element, you are declaring a function to be executed in response to a certain event, and you are doing that from within another function. Exactly the situation where closure can shine!

For example, what if you wanted to be able to track clicks or other events the same we tracked the number of times `ourFunction` was invoked? It should be no surprise that closure can do the job. 

## A simple voting app

To make this more concrete, let's consider a simple website for tracking votes (clicks). 

Note: We're dealing with front-end persistence only here, so "tracking" for our purposes means tracking within a single session. Any data will disappear when the page reloads.

![Color voting website with lightpink background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnktu72y2pwcf45rgvmq.png)

Here the user can select three color options for which to vote as many times as they wish. 

When you click the lightpink button, the background changes to lightpink and you can click the voting button to vote for lightpink. A counter of the current votes is displayed.

Similarly, when you click the lightblue button, the same thing happens. The background changes and you can now vote for lightblue and see a count of votes for lightblue.

![Color voting website with lightblue background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhjulqottbpjzg6mrc97.png)

Ditto for lightgreen. 

![Color voting website with lightgreen background](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yd0fpa57o9axu5oyscde.png)

Now the question: how do we get the vote counts for each color to persist such that votes for each color are counted accurately no matter how many times we switch between colors, voting as we wish for each? Three votes for lightgreen should stay three votes when I switch to light green, no matter how many times I vote for lightpink or lightblue in the interim.

Answer: closure. 

The problem is really not so different from tracking invocations of `ourFunction` and `theirFunction`.

## Code 

[Code for this section is on github](https://github.com/mattIshida/closure-event-listeners)

First we can create the bare bones of the voting website by iterating over an array of colors and calling a function on each.

```
const colors = ['lightpink', 'lightblue', 'lightgreen']
colors.forEach(addColorToDOM)
```

We simply need to define the `addColorToDOM` function.

The function needs to do three things: 

1. Add each color button to the page
2. Add functionality that changes the background color and displays votes in response to a click on the button
3. Counts votes (i.e. clicks on the voting button) correctly

The first item is straightforward. For each color we simply generate a new DOM element, set its text content, and append it to the correct HTML element, which we've assigned to the variable `colorList`.

```
// DOM selectors
const colorList = document.querySelector('#color-list')

// Render functions
function addColorToDOM(color){
    const choiceButton = document.createElement('button')
    choiceButton.textContent = color
    colorList.append(choiceButton)
}
```

The second is a bit more complicated but still pretty easy. We simply add a click event listener to our `choiceButton` element. 

```
// DOM selectors
const colorList = document.querySelector('#color-list')

// Render functions
function addColorToDOM(color){
    const choiceButton = document.createElement('button')
    choiceButton.textContent = color
    colorList.append(choiceButton)

    choiceButton.addEventListener('click', () => {
        document.body.style.backgroundColor = color
    })
}
```

Nice.

The click also needs to display the votes, so let's go ahead and declare a `voteCount` variable initialized to 0 and inject that content into the appropriate DOM element as well.

```
// DOM selectors
const colorList = document.querySelector('#color-list')
const votes = document.querySelector('#votes')

// Global variables
let voteCount = 0

// Render functions
function addColorToDOM(color){
    const choiceButton = document.createElement('button')
    choiceButton.textContent = color
    colorList.append(choiceButton)

    choiceButton.addEventListener('click', () => {
        document.body.style.backgroundColor = color
        votes.textContent = `Current votes for ${color}: 
         ${voteCount}`
    })
}
```

So far, so good. Now we have only have the third task of figuring out how to set `voteCount` correctly for each color.

As a first step, observe that each color should listen for clicks on the vote button, but only count votes if that color is the currently selected color. So we'll need to add an event listener on the `voteButton` element, plus a variable to track which color is currently selected

```
// DOM selectors
const colorList = document.querySelector('#color-list')
const votes = document.querySelector('#votes')
const voteButton = document.querySelector('#vote-btn')

// Global variables
let voteCount = 0
let currentSelection

// Render functions
function addColorToDOM(color){
    const choiceButton = document.createElement('button')
    choiceButton.textContent = color
    colorList.append(choiceButton)

    choiceButton.addEventListener('click', () => {
        currentSelection = color
        document.body.style.backgroundColor = color
        votes.textContent = `Current votes for ${color}: 
         ${voteCount}`
    })

    voteButton.addEventListener('click', ()=>{
        if(currentSelection === color) {
            voteCount++;
            votes.textContent = `Current votes for ${color}: 
            ${voteCount}`
        }
    })
}
```
We also need to modify the `choiceButton` callback to update the `currentSelection` variable. I've gone ahead and included those modifications in the code above as well. 

OK. It looks like we have all the pieces we'll need. Let's go ahead and test. 

I can easily add votes for lightpink.

![votes for lightpink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilmgm4wwgz2qsq51c5y8.png)

But as soon as I select lightblue the counter starts where lightpink left off and increments from there. FAIL.

![incorrect votes for lightblue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orxgixf32zrx0iio0n9v.png)

Hmm... we set up event listeners on each button with callbacks that increment the same global variable `voteCount`. What we actually need is a `voteCount` specific to each.

This is exactly the situation we faced with `ourFunction` and `theirFunction`. There too we had a `count` variable that both functions could access, and we needed a variable that was specific to each. We solved the problem by declaring the `count` variable in the unique execution context where each function was created and let closure do its thing.  

Let's do the same here. We simply move the declaration of `voteCount` from the global context to inside the `addcolorToDOM` function. 

```
// DOM selectors
const colorList = document.querySelector('#color-list')
const votes = document.querySelector('#votes')
const voteButton = document.querySelector('#vote-btn')

// Global variables
let currentSelection

// Render functions
function addColorToDOM(color){
    const choiceButton = document.createElement('button')
    choiceButton.textContent = color
    colorList.append(choiceButton)

    let voteCount = 0

    choiceButton.addEventListener('click', () => {
        currentSelection = color
        document.body.style.backgroundColor = color
        votes.textContent = `Current votes for ${color}: 
         ${voteCount}`
    })

    voteButton.addEventListener('click', ()=>{
        if(currentSelection === color) {
            voteCount++;
            votes.textContent = `Current votes for ${color}: 
            ${voteCount}`
        }
    })
}
```

Now, when addColorToDom gets called on a color, it creates a new execution context and a new voteCount variable. And we know from our mantra

> Functions retain access to variables in the context in which they were created.

that the callback functions created in the same execution context will retain access to those variables, even when the execution context goes away. 

When we test out the solution, it does indeed work to count votes for each color and persist them when you switch back and forth. 

I can start logging votes for lightpink:

![persisting votes for lightpink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kr4dxtd1v9k0rohl1xsl.png)

Switch to lightblue:

![persisting votes for lightpink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a2aecookgu4fafwi1bx.png)

And to lightgreen: 

![persisting votes for lightpink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbl88hik1c11ppofjeur.png)

Vote counts are accurate and remain so even when I toggle back and forth to my heart's content. 

## Takeaway

We were able to achieve front-end persistence without any data structure that explicitly logged votes for each of the three colors. Instead, we were able to enclose a color-specific `voteCount` variable within each callback function. Every time the callback function got invoked it retained access to its own `voteCount` variable. 

In fact the connection between closure and event listeners goes even deeper than what I've discussed here, but I'll leave that for another post.