Refactoring JQuery to ES6
Published: 2020-10-04 01:36:45

<figcaption><span>Cover Photo by <a href="https://unsplash.com/@markusspiske?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Markus Spiske</a> on <a href="https://unsplash.com/s/photos/code?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span></figcaption>

# A Little Look At The History Of JQuery
> The big bang of web development

Between 2006 and 2015, JQuery was revolutionary. It was a library which did everything that developers had a hard time maintaining code for: standardized API to select elements in HTML, and cross browser support.

Now in 2020, you as a reader might scoff at these features. But this was the reality for front end web developers before the appearance of ECMAscript 6 (ES6) and other important features in CSS3 you may take for granted.

# Why You Might Want To Remove JQuery As A Dependency
> Over-reliance can be an addiction

Dependencies are not a bad thing, we as developers do not need to code everything from scratch and "reinvent the wheel" every single time. However, I would argue that most usages of JQuery are trivial to accomplish in vanilla Javascript and do not require importing the entire JQuery library into your client's browser. In other words, we might want to remove this dependency to increase load speed and make better use of native browser capabilities if we can.

# The Demo Code
> Lead by example

So today I will be showing how I refactored several JQuery functions from my [HypeTracker](https://github.com/justinhodev/hype-tracker) project into vanilla Javascript.

```javascript
// private/js/ajax.js
// run functions after document has loaded
$('document').ready(function() {
    // ajax update shown products after select value changes
    $('#brand').change(function() {
        var brand_id = $(this).val();
        $.ajax({
            url: "./load_data.php",
            method: "POST",
            data: {brand_id : brand_id},
            success: function (data) {
                $('#show_product').html(data);
            }
        });
    });
    
    // update view if user enters characters into search box
    $('#sneaker_search').unbind().keyup(function(e) {
        var value = $(this).val();
        if (value.length >= 1) {
            search_sneaker(value);
        } else {
            $('#search_result').hide();
            $('#show_product').show();
        }
    });

    // ajax post call to return search results from search box
    function search_sneaker(val) {
        $('#show_product').hide();
        $('#search_result').show();
        $.post('./load_data.php', {
            'search_data' : val
        }, function(data) {
            if (data != "") {
                $('#search_result').html(data);
            } else {
                // default view if no results
                $('#search_result').html("<div class=\"row pl-3\"><div class=\"col-3\">No Result Found...</div></div>");
            }
        });
    }
});
```

I apologize in advance because the code is not great but it was also written several years ago and I've learned to write better code. The reason I'm using this code instead of a contrived example though is because it is code from an actual project with constraints, and there are already resources which tell you exact Javascript functions to use in place of JQuery (which I will link at the end).

# Breaking The Code Down
> Making it bite-sized

As with any project, even an old one I wrote myself, the first thing is having an understanding of what it does or is trying to accomplish. Reading through the code, I listed out the gist of what each section does:

1. An event is called when the value of a select element with the id `brand` changes
  - event: sends an AJAX `POST` request to `load_data.php` which returns some data to be shown in an element with the id `show_product`
2. An event is called when the input field with id `sneaker_search` is focused and a key from a detected keyboard is released
  - it calls a `search_sneaker` function when there are characters detected in the input field
  - otherwise it shows the element `show_product` and hides the element `search_result`
3. The function `search_sneaker` sends an AJAX `POST` request  to `load_data.php` and renders the return values in an element with the id `search_result`
4. Everything above is wrapped inside a helper function to make sure all code will not run until the document has fully loaded

# Tackling Changes One By One
> Baby steps, one at a time

To begin, I will start by extracting the AJAX requests. If you have not noticed, there are two instances in my breakdown above where I noted "sends an AJAX `POST` request to `load_data.php`". This means that I'm violating the DRY (don't repeat yourself) principle and can refactor it into one reusable function.

## Asynchronous Javascript And XML

A powerful proposition of JQuery was its streamlined AJAX request and response functions over the native `XMLHttpRequest`. However, the introduction of the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) changes this because it too was fairly easy to use, and supported the promise-based syntax. The last point makes it even better when you don't have to support IE with [the introduction of the `async / await` keywords in the browser](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await).

Since I'm not supporting anyone, really, I'll be making use of both the fetch API and the `async / await` to create my AJAX function.

First, I will use the fetch equivalent for JQuery's `$post(url, data)`, which is `fetch(url, { method: 'POST'})`.

Next, we can encode our data in two ways,
1. Manually encode it ourselves (ex. `field1=data1&field2=data2`)
2. Use the [`FormData` interface](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to do it for us

Using the `FormData` interface, we will end up with a new function like this:

```javascript
async function searchSneaker(type, value) {
  
  let searchData = new FormData();
  searchData.append(type, value);

  const response = await fetch('./load_data.php', {
    method: 'POST',
    body: searchData
  });

  if (!response.ok) {
    throw new Error(response.status);
  }

  const text = await response.text();
  return text;
}
```

As a quick side note, the fetch promise object does not throw errors on bad HTTP status by default. Instead, checking if the promise object's `ok` property is true (for HTTP status codes 200-299) or manually checking the return code of the `status` property.

## Element Selectors

The second part is relatively short but it'll help us consolidate our selectors in Javascript variables.

In JQuery, elements can be selected with the `$(element)` function. On the other hand, vanilla Javascript has `querySelector` and `getElementById`. Switching syntax and identifying the elements I needed from above, I will end up with these global variables:

```javascript
const products = document.getElementById('show_product');
const searchResult = document.getElementById('search_result');
const searchBox = document.getElementById('sneaker_search');
const brandSelector = document.getElementById('brand');
```

## Event Listeners

The final step to finish the refactoring is to use native [event listeners](https://developer.mozilla.org/en-US/docs/web/api/eventlistener). In JQuery, we chained together element selectors along with event listeners such as `$(element).on(...)` or `$(element).change(...)`. The native event listener interface is not too different other than having to specify the type of event as an argument.

```javascript
document.onreadystatechange = function () {
  if (document.readyState === 'complete') {
    searchBox.addEventListener('input', filterBySearch);
    brandSelector.addEventListener('change', filterByBrand);
  }
}
```

The `filterBySearch` and `filterByBrand` variables are actually functions passed as an argument. In this case, the corresponding functions will receive an `Event` object as a parameter. This chunk of code takes care of making sure no AJAX functions are dispatched until the document has fully loaded by attaching the event listeners at that time.

# The Completed Code
> Glad you made it this far

After making changes in these 3 categories and using JSDoc style comments, I ended up with code like this:

```javascript
const products = document.getElementById('show_product');
const searchResult = document.getElementById('search_result');
const searchBox = document.getElementById('sneaker_search');
const brandSelector = document.getElementById('brand');

document.onreadystatechange = function () {
  if (document.readyState === 'complete') {
    searchBox.addEventListener('input', filterBySearch);
    brandSelector.addEventListener('change', filterByBrand);
  }
}

/*
 * Limit Products by Brand Select
 *
 * @param {Event} brand - value from select event
 * @return {void} updates product html
 *
 */
async function filterByBrand(brand) {
  brandId = brand.target.value;
  const sneakers = await searchSneaker('brand_id', brandId);
  if (sneakers === null) return;
  products.innerHTML = sneakers;
}

/*
 * Limit Products by Search Phrase
 *
 * @param {Event} search - value from input event
 * @return {void} updates search result html
 *
 */
async function filterBySearch(search) {

  let searchValue = search.target.value;

  if (searchValue.length <= 0) {
    searchResult.style.display = 'none';
    products.style.display = 'block';
    return;
  }
  
  const sneakers = await searchSneaker('search_data', searchValue);

  if (sneakers !== "") {
    searchResult.innerHTML = sneakers;
    return;
  }

  searchResult.innerHTML = "<div class=\"row pl-3\"><div class=\"col-3\">No Result Found...</div></div>";
}

/*
 * Send an async request with search term
 *
 * @param {string} type - search type
 * @param {string} value - search value
 * @return {text} the formatted HTML with data
 *
 */
async function searchSneaker(type, value) {
  
  let searchData = new FormData();
  searchData.append(type, value);

  const response = await fetch('./load_data.php', {
    method: 'POST',
    body: searchData
  });

  if (!response.ok) {
    throw new Error(response.status);
  }

  const text = await response.text();
  return text;
}
```

I omitted explaing the `filterByBrand` and `filterBySearch` functions since they're basic Javascript. One last note is that the `innerHTML` property is the same as the `html` property in JQuery.

# Wrapping Up

I hoped you learned something from reading about my process in refactoring this code, whether it be that you can write better code or that it's pretty easy to convert from JQuery to native Javascript.

I'll leave some links for reference and further readings in case you were still confused.

Feel free to connect with me on Twitter [@justinhodev](https://twitter.com/justinhodev) and happy coding!

# Resources

[History of JQuery](https://blog.logrocket.com/the-history-and-legacy-of-jquery/)
[You Might Not Need JQuery](http://youmightnotneedjquery.com/)
[Essential Cheatsheet: Convert JQuery to Javascript](https://webdesign.tutsplus.com/articles/essential-cheat-sheet-convert-jquery-to-javascript--cms-35633)
[How to use fetch with async / await](https://dmitripavlutin.com/javascript-fetch-async-await/)