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.
On this page · 6 min · Advanced
On this page
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Advanced
- Updated
- Oct 28, 2026
Related tutorials
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#
<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>
attr: undefined | prop: true exists? false right after call: undefined clicked once in callback: 42
Step-by-step explanation#
- bind() is called twice, but .off("click.demo") removes the old handler first, so the click logs once.
- Setting the checkbox with .prop changes the live state; the checked attribute stays undefined.
- A normal function handler gets the element as this.
- An empty selection has length 0 — the only reliable existence test.
- 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#
Wrapping code in try/catch to hide “is not a function” leaves the real cause (missing script) in place. Fix the load order.
It works but hides structure and slows every click. Delegate from the right container.
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?
Show solution
<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>
Mini quiz#
A click handler runs twice per click. What is the likely cause?
Want more? Practise all jQuery MCQs · jQuery interview questions
My notes#
Saved in this browser.