26 useful jQuery tips, with modern JavaScript equivalents
Maintain an existing jQuery site with clearer event handling, safer DOM updates, and small patterns you can gradually replace with browser APIs.
jQuery remains part of many established websites. The sensible maintenance question is not whether every line should be rewritten today. It is whether a change makes behavior easier to understand, keeps dependencies supported, and avoids introducing new risks.
These are newly written examples for this restored resource. Check the jQuery API documentation against the version in your project. Modern equivalents assume the browsers you support implement the relevant API.
Selecting and updating elements
1. Wait until the DOM is ready
Use $(function () { /* setup */ }); if your jQuery script can execute before the markup exists. In a new page, a module script is deferred by default, so code can usually query elements without a separate ready callback.
2. Keep selections you reuse
Store const $panel = $('#settings'); if several lines operate on the same panel. This clarifies which element is being changed. Re-query if the node is replaced later; a cached reference does not magically point to a replacement.
3. Check that an element exists
if ($panel.length) { /* update */ } makes optional UI explicit. In native JavaScript, document.querySelector('#settings') returns null when nothing matches. Optional chaining is useful when doing nothing is genuinely the intended behavior.
4. Use text for untrusted strings
Set $('.message').text(serverMessage), not .html(serverMessage). The equivalent is element.textContent = serverMessage. A response from your own server can still contain user-supplied content.
5. Toggle a class instead of many styles
$panel.toggleClass('is-open', open) keeps presentation in CSS. Native code can use panel.classList.toggle('is-open', open). Also update the controlling button’s aria-expanded state.
6. Read form state with properties
Use $('#subscribe').prop('checked') for the checkbox’s current state. The original checked attribute describes initial markup. Native code uses checkbox.checked.
7. Read and write input values deliberately
$('#name').val() reads the current field value. Calling .val('Ada') changes it but does not automatically fire a user change event. If other code must respond, call an explicit shared update function rather than depending on a synthetic event accidentally.
8. Prefer data attributes for small identifiers
button.dataset.projectId works well for an ID needed by a click handler. jQuery’s .data() caches values, so mixing it with later attribute mutations can be surprising. Pick one approach for an element and use it consistently.
Events without surprises
9. Delegate events from a stable parent
$('#results').on('click', '.remove', function () {
$(this).closest('.result').remove();
});
This also handles matching buttons inserted later. Attach to the narrowest stable container rather than delegating every event from the entire document.
10. Name related event handlers
$button.on('click.settings', handler) can later be paired with $button.off('.settings'). Namespaces help clean up a widget without removing unrelated listeners installed by another component.
11. Prevent only the default you replace
Use event.preventDefault() when handling a form submission yourself. Avoid returning false from every handler: in jQuery that also stops propagation, which may break another component’s behavior.
12. Preserve native link behavior
Do not intercept an ordinary link just to call window.location. A real href already supports opening in another tab, copying the address, and keyboard activation. JavaScript should enhance that behavior only when the enhancement is useful.
13. Handle a form’s submit event
Listen to submit, not only to a submit button’s click. People can submit a form by pressing Enter, and a form may have multiple submit buttons. Keep server-side validation even when the browser validates fields.
14. Use one-time listeners where appropriate
$element.one('click', handler) handles an event once per matched element. In native code, use addEventListener('click', handler, { once: true }). This is useful for initialization, not for actions users should be able to retry.
15. Debounce expensive search work
let timer;
$('#search').on('input', function () {
const query = this.value;
clearTimeout(timer);
timer = setTimeout(() => search(query), 200);
});
The example assumes search is your application function. Handle stale results separately: debouncing alone does not stop an earlier request from finishing after a newer one.
16. Clean up when a widget is removed
Remove document-level listeners, timers, observers, and pending requests created by a component. Removing its DOM node does not cancel resources attached elsewhere.
Requests and rendering
17. Handle failure as a normal outcome
With $.ajax(), provide both a success path and a .fail() handler. Restore the loading indicator in .always(). Show a concise error and keep a retry path rather than leaving the interface permanently disabled.
18. Check HTTP status when using fetch
async function loadProjects(signal) {
const response = await fetch('/api/projects', { signal });
if (!response.ok) throw new Error('Could not load projects');
return response.json();
}
This assumes an application endpoint exists. A 404 or 500 response does not automatically reject fetch; handle it before interpreting the body.
19. Cancel a superseded request
An AbortController lets you cancel a previous fetch before starting another search. Treat intentional cancellation differently from a network failure. You may also need a request sequence number to ensure only the newest result updates the view.
20. Build content off the document
For several new nodes, use a DocumentFragment and append once. Build elements with createElement and textContent when data is untrusted. Do not assume a particular performance benefit; measure your actual page.
21. Keep loading state accessible
Set aria-busy="true" on the changing region and announce an informative result in a restrained aria-live="polite" area. Avoid announcing every character typed or every DOM node inserted.
22. Disable duplicate submissions carefully
Disable the submit control while a request runs and restore it on failure. For consequential writes, use a server-side idempotency mechanism as well. A disabled button cannot prevent retries, double requests, or multiple browser tabs by itself.
Motion, accessibility, and maintenance
23. Prefer CSS for simple transitions
A class-based transition is easier to inspect than a queue of imperative animations. Animate opacity or transforms when appropriate, but do not animate a layout merely because it is possible.
24. Respect reduced motion
Wrap optional animation rules in @media (prefers-reduced-motion: no-preference). A user who prefers reduced motion should still receive the same information and controls.
25. Use real buttons for actions
A <button type="button"> already supports keyboard interaction and has the correct semantics. A clickable <div> requires you to rebuild behavior that the browser already provides. An action and a navigation link are different controls.
26. Migrate at the boundary of a feature
Replace one self-contained widget at a time, keeping its inputs, outputs, and accessible behavior stable. Inventory plugins before removing jQuery itself. A small dependency still in use can make an apparently successful removal fail on a rarely visited page.
A useful maintenance rule
Before changing a legacy component, write down what keyboard users can do, what happens on a slow connection, and what happens when a request fails. Preserve those behaviors during refactoring. Shorter code is helpful; predictable code is more important.