Skip to main content
Home / Articles /

Making the Move from jQuery to Vue

As someone who has used jQuery for many. years and has recently become a Vue convert, I thought it would be an interesting topic to discuss the migration process of working with one to the other.

Before I begin though, I want to ensure one thing is crystal clear. I am not, in any way whatsoever, telling anyone to stop using jQuery. That's been pretty fashionable lately, and heck, I wrote up something similar myself a few year ago ("How I'm (Not) Using jQuery"). If you get things done with jQuery and your end users are successfully using your site, then more power to you. Keep using what works for you.

This guide is more for people who may be coming from years of jQuery experience and want to see how things can be done with Vue. With that in mind, I'm going to focus on what I consider "core" jQuery use cases. I won't cover every single possible feature but instead take a "I often did [X] with jQuery" approach that may be more relatable to people considering learning Vue. (As an aside, also note that how I write my examples are simply one way of performing a task. Both jQuery and Vue give provide multiple ways to accomplish the same goal and that's a great thing!)

With that in mind, let's consider some high level things we might turn to jQuery for:

  • Finding something in the DOM (with the idea of doing something with it later)
  • Changing something in the DOM (e.g. the text of a paragraph or the class of a button)
  • Reading and setting form values
  • Form validation (which is really just a combination of the items above)
  • Ajax calls and handling the results
  • Event handling (e.g. on button click, do something)
  • Measuring or changing the styles of an element

There's more to jQuery, of course, but these uses (at least in my opinion), cover the most common use cases. Also note there's a lot of cross pollination in the list above. So, should we start with a simple one-to-one comparison of each? Nope, not so fast. Let's begin by covering the major difference in Vue applications.

Defining where Vue "works"

When we drop jQuery onto a page, we are basically adding a Swiss Army knife to JavaScript code to handle common web development tasks. We can do any of the uses case we’re going to cover in whatever order we see fit. For example, a client may ask for form validation today, then in a month or so, ask to do an Ajax-based search form in the header of the site.

Vue has one significant difference in that respect. When starting a with Vue project, we start by defining an "area" in the DOM we want it to focus on. So, let’s consider a simple prototype web page:

<body>

  <header>
    Fancy header stuff here
  </header>

  <div id="sidebar">
    Some sidebar doohicky here
  </div>

  <main>
    <p>
      Main site content here, super important stuff...
    </p>
    <div id="loginForm">
      A login form of course
    </div>
  </main>

</body>

In a typical jQuery application, we may write code to work with the header, sidebar, and login form or something. No big whoop:

$(document).ready(function() {

  $('header') // something...

  $('#sidebar') // something...

  $('#loginForm') // something... 

});

In a Vue application, we first specify what are we're working with. Imagine our client first asked to add validation to the loginForm element. Our Vue code would specify that:

new Vue({
  el: '#loginForm',
  // Code here...
});

This means that we’d typically end up adding a second Vue application if the client later decides to have us add something to the sidebar:

new Vue({
  el:'#loginForm',
  // Code here...
});

new Vue({
  el:'#sideBar',
  // Code here...
});

Is that a bad thing? Absolutely not. Right away, we get the benefit of encapsulation. If we accidentally use a variable with a generic name (we've all done that), we don't have to worry about conflicts with other parts of your code. Later on when the client adds yet another request, having our unique, logical sets of Vue code separated out like this gives us some great peace of mind that things won't step on each other.

So, yes, a good thing. But it absolutely caused me to stop a bit when I first began using Vue. Now, onto our use cases.

Finding Stuff in the DOM

Another aspect you'll find interesting, or scary, is how to "find stuff in the DOM." That's a bit vague, but let's consider a firm example. We have a button, and when it’s clicked, we make something happen. Here's an abbreviated example of how this could look:

<button id="myButton">Click Me!</button>
<!-- More stuff... -->
<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    alert(1);
  });

});
</script>

Now let's compare that to how it can be done with Vue:

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
</div>

<script>
const app = new Vue({
  el:'#app',
  methods: {
    doSomething: function() {
      alert(1);
    }
  }
});
</script>

The Vue application is a bit more verbose, but note how the markup has a direct connection between the action ("click") and the function that will be called. Vue's code doesn't have a tie back to the DOM (outside of the el portion where we define where it needs to work). This was easily one of the things that sold me on Vue the most — it feels easier to tell what is going on. Also, I didn't need to worry so much about the ID value and selectors. If I change the class or ID of the button, I don't need to go back into my code and worry about updating selectors.

Let's consider another example: finding and changing text in the DOM. Imagine that button, on click, now changes the text of another part of the DOM.

<button id="myButton">Click Me!</button>
<span id="result"></span>

<!-- More stuff... -->

<script>
$(document).ready(function() {

  $('#myButton').click(function() {
    $('#result').text('You clicked me, thanks!');
  });

});
</script>

I've added a new span and now, when the button is clicked, we use another selector to find it and use a jQuery utility method to change the text inside it. Now consider the Vue version:

<div id="app">
  <button v-on:click="doSomething">Click Me!</button>
  <!-- On click, change text in this span -->
  <span>{{resultText}}</span>
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    resultText: ''
  },
  methods: {
    doSomething: function() {
      this.resultText = 'You clicked me, thanks!';
    }
  }
});
</script>

In this example, we're using Vue's template language (the highlighted line) to specify that we want to render a variable inside the span, which is resultText in this case. Now, when the button is clicked, we change that value and the span's inner text will change automatically.

As an aside, Vue supports a shorthand for the v-on attribute, so the button in the example could have been written with @click="doSomething" instead.

Reading and writing form variables

Working with forms is probably one of the most common — and useful — things that we can do with JavaScript. Even before JavaScript, most of my early "web development" was writing Perl script to handle form submissions. As the primary way of accepting user input, forms have always been critical to the web and that's probably going to stay the same for quite some time. Let's consider a simple jQuery example of reading a few form fields and setting another:

<form>
  <input type="number" id="first"> + 
  <input type="number" id="second"> =
  <input type="number" id="sum"> 
  <button id="sumButton">Sum</button>
</form>

<script>
$(document).ready(function() {
  let $first = $('#first');
  let $second = $('#second');
  let $sum = $('#sum');
  let $button = $('#sumButton');
  
  $button.on('click', function(e) {
    e.preventDefault();
    let total = parseInt($first.val(),10) + parseInt($second.val(),10);
    $sum.val(total);
  });
});
</script>

This code demonstrates how jQuery can both read and write via the val() method. We end up getting four items from the DOM (all three form fields and the button) and use simple math to generate a result. Now consider the Vue version:

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
  <button @click.prevent="doSum">Sum</button>
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0,
    sum: 0
  },
  methods: {
    doSum: function() {
      this.sum = this.first + this.second;
    }
  }
})
</script>

This introduces some interesting Vue shortcuts. First, v-model is how Vue creates two way data binding between values in the DOM and in JavaScript. The data block variables will automatically sync up with the form fields. Change the data, and the form updates. Change the form, and the data updates. The .number is a flag to Vue to treat the inherit string values of form fields as numbers. If we leave this off and do addition as we are, we'll see string additions and not arithmetic. I've been working with JavaScript for nearly a century and still screw this up.

Another neat "trick" is @click.prevent. First, @click defines a click handler for the button, then the .prevent portion blocks the browser’s default behavior of submitting the form (the equivalent of event.preventDefault()).

The final bit is the addition of the doSum method that's bound to that button. Note that it simply works with the data variables (which Vue makes available in the this scope).

While this is mostly my personal feeling here, I really love the lack of query selectors in the script when writing in Vue and how the HTML is much more clear about what it's doing.

Finally, we could even get rid of the button completely:

<form id="myForm">
  <input type="number" v-model.number="first"> + 
  <input type="number" v-model.number="second"> =
  <input type="number" v-model="sum"> 
</form>

<script>
new Vue({
  el: '#myForm',
  data: {
    first: 0,
    second: 0
  },
  computed: {
    sum: function() {
      return this.first + this.second;
    }
  }
})
</script>

One of the cooler features of Vue is computed properties. They are virtual values that recognize when their derived values are updated. In the code above, as soon as any of the two form fields change, the sum will update. This works outside of form fields too. We could render the sum like so:

The total is {{sum}}.

Working with Ajax

It’s commendable how easy jQuery has made it to use Ajax. In fact, I can say I've done Ajax "the vanilla" way probably a grand total of one time. (If you're curious, you can take a look at the spec for XMLHttpRequest and probably be happy you avoided it yourself.) jQuery's simple $.get(...) method worked in a large number of cases and when it’s needed for something more complex, $.ajax() made it easy as well. Another thing jQuery did well is the way it handles JSONP requests. While mostly unnecessary now with CORS, JSONP was a way to handle making requests to APIs on different domains.

So, what does Vue do for you to make Ajax easier? Nothing!

OK, that sounds scary but it really isn't. There are many options out there for working with HTTP requests, and Vue.js took a more agnostic route of letting us, the developers, decide how we want to handle it. So yes, that does mean a bit more work, but we've got some great options.

The first one to consider is Axios, this is a Promise-based library that is very popular among the Vue community. Here's a simple example of it in action (taken from their README file):

axios.get('/user?ID=12345')
  .then(function (response) {
    // handle success
    console.log(response);
  })
  .catch(function (error) {
    // handle error
    console.log(error);
  })
  .then(function () {
    // always executed
  });

Axios supports POST requests, of course, and lets us specify headers among many other options.

While Axios is very popular among Vue developers, it isn't something that really clicked with me. (At least not yet.) Instead, I've been much more a fan of Fetch. Fetch is not an external library but is a web standard way of performing HTTP requests. Fetch has very good support at roughly 90% of browsers, though that means it isn't completely safe to use, but we can always use a polyfill we need to.

While it's totally out of the scope of what we're discussing here, Kingsley Silas has written an excellent guide on using both Axios and Fetch with React.

Like Axios, Fetch is Promise-based and has a friendly API:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

Both Axios and Fetch cover all types of HTTP requests, so either will fit an any number of needs. Let's look at a simple comparison. Here's a simple jQuery demo that makes use of the Star Wars API.

<h1>Star Wars Films</h1>
<ul id="films">
</ul>

<script>
$(document).ready(function() {
  $.get('https://swapi.com/api/films', function(res) {
    let list = '';
    res.results.forEach(function(r) {
      list += `<li>${r.title}</li>`;
    });
    $('#films').html(list);
  });
});
</script>

In the sample above, I use $.get to hit the API and return a list of films. Then I generate a list of titles as li tag elements with that data and insert it all into a ul block.

Now, let's consider an example of this using Vue:

<div id="app">
  <h1>Star Wars Films</h1>
  <ul>
    <li v-for="film in films">{{film.title}}</li>
  </ul>  
</div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    films: []
  }, 
  created() { 
    fetch('https://swapi.com/api/films')
    .then(res => res.json())
    .then(res => {
      this.films = res.results;  
    });
  }
})
</script>

Probably the best part of this is the use of the v-for template. Notice how Vue isn't concerned with the layout (well, at least the JavaScript). The data is fetched from the API. It’s assigned a variable. The layout handles displaying it. I've always hated having HTML in my JavaScript and, while solutions exist for that with jQuery, having it baked into Vue makes it a natural fit.

A full (if somewhat trivial) example

To bring it home a bit, let's consider a more real world example. Our client has asked us to build a fancy Ajax-enabled front-end search interface to a product API. The feature list includes:

  • Support filtering by name and product category
  • Form validation such that we must supply a search term or a category
  • While the API is being hit, show a message to the user and disable the submit button
  • When done, handle reporting that no products were shown or list the matches

Let's begin with the jQuery version. First, the HTML:

<form>
  <p>
    <label for="search">Search</label>
    <input type="search" id="search">
  </p>
  <p>
    <label for="category">Category</label>
    <select id="category">
      <option></option>
      <option>Food</option>
      <option>Games</option>
    </select>
  </p> 
  <button id="searchBtn">Search</button>
</form>

<div id="status"></div>
<div id="results"></div>

There's a form with our two filters and two divs. One's used as a temporary status when searching or reporting errors and one is used to render results. Now, check out the code.

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

$(document).ready(() => {
  let $search = $('#search');
  let $category = $('#category');
  let $searchBtn = $('#searchBtn');
  let $status = $('#status');
  let $results = $('#results');
  
  $searchBtn.on('click', e => {
    e.preventDefault();
    
    // First clear previous stuff
    $status.html('');
    $results.html('');

    // OK, now validate form
    let term = $search.val();
    let category = $category.val();
    if(term === '' && category === '') {
      $status.html('You must enter a term or select a category.');
      return false;
    }

    $searchBtn.attr('disabled','disabled');
    $status.html('Searching - please stand by...');
    
    $.post(productAPI, { name:term, category:category }, body => {
      $searchBtn.removeAttr('disabled');
      $status.html('');

      if(body.results.length === 0) {
        $results.html('<p>Sorry, no results!</p>');
        return;
      }
      
      let result = '<ul>';
      body.results.forEach(r => {
        result += `<li>${r.name}</li>`
      });
      result += '</ul>';
      $results.html(result);
    });
    
  });
});

The code begins by creating a set of variables for each of the DOM items we want to work with — the form fields, button, and divs. The core of the logic is within the click handler for the button. We do validation, and if everything is OK, do a POST request against the API. When it returns, we either render the results or show a message if nothing was matched.

You can work with a complete version of this demo using the CodePen below.

See the Pen
jQuery Full
by Raymond Camden (@cfjedimaster)
on CodePen.

Now let's consider the Vue version. Again, let's start with the layout:

<div id="app">
  <form>
    <p>
      <label for="search">Search</label>
      <input type="search" v-model="search">
    </p>
    <p>
      <label for="category">Category</label>
      <select v-model="category">
        <option></option>
        <option>Food</option>
        <option>Games</option>
      </select>
    </p>
    <button @click.prevent="searchProducts" :disabled="searchBtnDisabled">Search</button>
  </form>

    <div v-html="status"></div>
    <ul v-if="results">
      <li v-for="result in results">{{result.name}}</li>
    </ul>
</div>

From the top, the changes include:

  • Wrapping the layout in a div that can be used to let Vue know where to work.
  • Using v-model for the form fields to make it easy to work with the data.
  • Using @click.prevent to handle doing the main search operation.
  • Using :disabled to bind whether or not the button is disabled to a value in the Vue application (we'll see that in action in a moment).
  • The status value is a bit different than earlier examples. While jQuery has a specific method to set text in a DOM item and another for HTML, Vue requires using v-html when assigning HTML to a value that's going to be rendered. If we tried to do {{status}} with HTML, the tags would be escaped.
  • Finally, using v-if to conditionally render a list of results along with v-for to handle the iteration.

Now let's look at the code.

const productAPI = 'https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/productSearch';

const app = new Vue({
  el: '#app',
  data: {
    search: '',
    category: '',
    status: '',
    results: null,
    searchBtnDisabled: false
  },
  methods: {
    searchProducts:function() {
      this.results = null;
      this.status = '';
      
      if(this.search === '' && this.category === '') {
        this.status = 'You must enter a term or select a category.';
        return;
      }

      this.searchBtnDisabled = true;
      this.status = 'Searching - please stand by...';
      
      fetch(productAPI, {
        method: 'POST',
        headers: {
          'Content-Type':'application/json'
        },
        body: JSON.stringify({name:this.search,category:this.category})
      }).then(res => res.json())
      .then(res => {
        this.status = '';
        this.searchBtnDisabled = false;
        this.results = res.results;
        if(this.results.length === 0) this.status = '<p>Sorry, no results!</p>';
      });
      
    }
  }
});

The first block worth calling out is the set of data fields. Some map to form fields and others to results, status messages, and the like. The searchProducts method handles much of the same stuff as the jQuery version but, in general, there's much less code directly tied to the DOM. For example, even though we know the results are listed in an unordered list, the code itself doesn't worry about that. It simply assigns the value and the markup handles rendering it. Overall, the JavaScript code is much more concerned about logic in comparison to the jQuery code which "feels" like a much nicer separation of concerns.

As before, I've got a CodePen for you to try this out yourself:

See the Pen
Vue Full
by Raymond Camden (@cfjedimaster)
on CodePen.

Death to jQuery! Long Live Vue!

OK, that's a bit over the top. As I said in the beginning, I absolutely think that you shouldn't change a thing if like working with jQuery and it's working for you.

I can say, however, that Vue feels like a great "next step" for people who are used to working with jQuery. Vue supports complex applications and has a great command line tool for scaffolding and building projects. But for simpler tasks, Vue works as a great "modern jQuery" replacement that has become my tool of choice for development!

For another perspective on using Vue in place of jQuery, check out Sarah Drasner's "Replacing jQuery With Vue.js: No Build Step Necessary" because it includes a handful of other super helpful examples.