jQuery · Practice

Common Mistakes

Most jQuery bugs come from the same ten mistakes. Learn to recognise their symptoms and you will fix them in minutes instead of hours.

Advanced 6 min Lesson 21 of 22 Runnable examplesExamples use jQuery 3.x.
Before you start:jQuery Best Practices
0/22 in jQuery
On this page · 6 min · Advanced

Learning objectives#

  • Recognise common jQuery error messages
  • Fix event-handler bugs (doubles, missing on new elements, wrong this)
  • Fix state bugs (attr vs prop, data caching)
  • Fix async bugs with AJAX results

Explanation#

“$ is not defined” → jQuery not loaded yet, or loaded after your script. “$(...).x is not a function” → plugin missing, or jQuery loaded twice.

A click fires twice → the handler was bound twice (inside another handler or after each AJAX load). Use .off() first or delegate once.

Buttons on new rows do nothing → handlers were bound directly before the rows existed. Delegate from the parent.

this is window/undefined → arrow function used as a handler. Use function () {} or e.currentTarget.

Checkbox state wrong → .attr("checked") instead of .prop("checked"). Data not updating → .data() caches values; change with .data(key, value) or read .attr("data-x").

Variable is undefined after $.ajax → the code ran before the response. Use .done() or await.

Code example: Five bugs, five fixes#

HTMLFive bugs, five fixes Runnable
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<div style="font-family:sans-serif">
  <label><input type="checkbox" id="agree"> I agree</label>
  <ul id="list"><li><button class="hi">Hi</button></li></ul>
</div>
<script>
  $(function () {
    // Bug 1 fixed: bind once, removing any earlier handler with the same namespace
    function bind() { $('#list').off('click.demo').on('click.demo', '.hi', () => console.log('clicked once')); }
    bind(); bind();


    // Bug 2 fixed: prop, not attr, for current checkbox state
    $('#agree').prop('checked', true);
    console.log('attr:', $('#agree').attr('checked'), '| prop:', $('#agree').prop('checked'));


    // Bug 3 fixed: normal function so this is the element
    $('#agree').on('change', function () { console.log('this is', this.id); });


    // Bug 4 fixed: empty selection is still an object — check length
    console.log('exists?', $('.nope').length > 0);


    // Bug 5 fixed: use AJAX data inside the callback (simulated with a Deferred)
    let result;
    const fakeAjax = $.Deferred(d => setTimeout(() => d.resolve(42), 50)).promise();
    console.log('right after call:', result);
    fakeAjax.done(value => { result = value; console.log('in callback:', result); });


    $('#list .hi').trigger('click');
  });
</script>
Expected output
attr: undefined | prop: true
exists? false
right after call: undefined
clicked once
in callback: 42
Opens this example in the full playground

Step-by-step explanation#

  1. bind() is called twice, but .off("click.demo") removes the old handler first, so the click logs once.
  2. Setting the checkbox with .prop changes the live state; the checked attribute stays undefined.
  3. A normal function handler gets the element as this.
  4. An empty selection has length 0 — the only reliable existence test.
  5. The value only exists inside the callback; reading it right after the call gives undefined.

Real-world use case#

A support engineer gets “the Save button saves twice”. Knowing the double-binding pattern, they find a .on("click") inside an AJAX success handler and fix it with delegation in minutes.

Common mistakes#

Silencing errors with try/catch

Wrapping code in try/catch to hide “is not a function” leaves the real cause (missing script) in place. Fix the load order.

Using $(document).on everywhere to “fix” handlers

It works but hides structure and slows every click. Delegate from the right container.

Mixing jQuery versions

Old plugins expecting jQuery 1.x can break with 3.x (removed .live(), .size(), .bind defaults). Use jQuery Migrate to find them.

Best practices#

  • Read the console error first
  • Check load order in the Network tab
  • Use .off() or delegation to avoid duplicate handlers
  • Use jQuery Migrate when upgrading

Remember this#

Not defined / not a function → load order or double jQuery.

Fires twice → bound twice. Dead on new rows → delegate.

Undefined after AJAX → you read it too early.

Practice exercise#

Your task: This code logs "undefined". Fix it: let name; $.getJSON("https://jsonplaceholder.typicode.com/users/1", u => name = u.name); console.log(name);

Need a hint?
Move the console.log into the callback, or use await.
Show solution
HTML Runnable
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
  $(async function () {
    const user = await $.getJSON('https://jsonplaceholder.typicode.com/users/1');
    console.log(user.name);   // Leanne Graham
  });
</script>
Opens this example in the full playground

Mini quiz#

Common Mistakes quizMultiple choice Question 1 of 3

A click handler runs twice per click. What is the likely cause?

Score so far: 0/0

Want more? Practise all jQuery MCQs · jQuery interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.